You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit aafd153
Browse filesBrowse the repository at this point in the historyBrowse files
Give the merge and issue gh runners the allowlisted environment (#86)
* Give the merge and issue gh runners the allowlisted environment
GhMergeGateway and GhIssueGateway ran gh with the whole server
environment. Their default runners now pass env: ghEnvironment(), the
same as the pull request and already-fixed adapters.
A new test runs each adapter's default runner against a fake gh that
prints its environment. It checks that an unrelated variable does not
reach gh. Before this change it failed for merge and issues.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Check that every allowlisted gh variable reaches gh; fix doc wording
Independent review findings:
- The runner test now sets every GH_ENV_ALLOWLIST variable and checks
each one and the fixed settings reach gh, so a runner that passes too
little (and would break sign-in) fails too.
- PATH and other changed variables are restored exactly, including
when they were unset.
- The docs now say gh also gets fixed settings that turn off prompts,
the pager, colour and update checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Take the fixed gh settings from ghEnvironment in the runner test
#84 now sets GH_PAGER per platform (empty on Windows, cat elsewhere).
The test compares against ghEnvironment({}) instead of a hard-coded
list, so it follows the helper's own values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stop the merge and issue gh runners before a cancelled call returns
GhMergeGateway and GhIssueGateway ran gh through promisified execFile.
On abort it sends SIGTERM and rejects at once, without waiting for gh to
exit. A gh that ignores SIGTERM kept running, so a cancelled
`gh pr merge` could still merge after the server reported it stopped.
Both now use runWithInput, like the pull request and already-fixed
adapters: SIGTERM, then SIGKILL after the grace period, and the promise
settles only after the process has exited.
A new test runs each default runner against a fake gh that ignores
SIGTERM, aborts the call, and checks that gh has exited by the time the
call rejects. It failed for merge and issues before this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Count the gh stop wait inside the merge and issue deadlines
Independent review round 1 on #86:
- runWithInput's default 5 s kill grace and 1 s pipe grace let an
aborted merge settle up to 20 s after it started, past the 15-second
request budget (AGENTS.md: count shutdown grace inside the deadline).
The merge and issue runners now use 0.5 s and 0.25 s: 14.75 s and
12.75 s. A test checks the settle time against these grace periods.
- The merge doc said a cancelled gh pr merge cannot go on to merge. A
request gh already sent can still be applied by GitHub; the doc now
says such a merge is reported as unknown until it is reconciled.
- The issue doc no longer mentions a per-command timeout the issue
runner does not have.
- The stuck-gh test now checks the abort reason and stops waiting for
the fake gh after 5 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the gh stop waits against the request budget
Independent review round 2 on #86:
- The stuck-gh test's grandchild released the pipes on its own after
about 0.5 s, so dropping pipeGraceMs still passed. The fake gh now
starts a process that holds the pipes for 30 s, so the call settles
only through both grace periods, and the test kills it afterwards.
- A test now checks that each deadline plus its stop wait stays below
the 15-second request budget. The merge operation deadline and the
issue refresh deadline are named constants the test imports.
- Comments and docs say the stop wait comes on top of the deadline,
that the merge constants cover every gh call of the gateway, and that
an unknown direct merge stays disabled until GitHub shows it merged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep a merge click's stop wait inside the shutdown drain
Independent review round 3 on #86:
- A merge click admitted just before shutdown could settle at 14.75 s,
after the 14.5 s shutdown drain. The merge runner's grace periods are
now 0.25 s and 0.15 s, so a click settles within 14.4 s. The drain
limit is a named constant, and the budget test checks the merge click
against it.
- The stuck-gh test stops the call and the fake gh in onTestFinished, so
a failure or test timeout leaves no process behind.
- The merge doc says only a direct attempt waits for GitHub to show it
merged; a queued attempt also settles on removal or failure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name the merge inspection deadline and test it against the budget
Independent review round 4 on #86:
- The 12 s inspection deadline was a literal in five places, so the
comment's "inspections settle within 12.4 s" had no test. It is now
MERGE_INSPECTION_TIMEOUT_MS, the default and the cap of every merge
inspection, and the budget test checks it plus the stop wait.
- The merge doc no longer says a stopped gh cannot send anything more;
it says stopping gh cannot recall a request it already sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Say which inspections default to the 12-second deadline
The queue watermark defaults to 6 s; 12 s is only its cap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/implementation/guarded-merge.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ Merging blocks when any plan item is unreviewed or stale, an attributed file is
10
10
11
11
The GitHub adapter reads the current PR base/head, effective branch rulesets, classic branch protection, required check contexts and app identities, and issue cross-references. It ignores review records that are not required checks. Cross-referenced PRs are read in one GraphQL request; repository identity is preserved with each PR number, and cross-repository references fail closed. More than 100 references also fail closed as unknown. Malformed pagination or PR references, partial referenced-PR records, rules without a type, malformed protection objects, missing or malformed check-app identities, and inconsistent check status/conclusion pairs fail closed, while an explicit `null` remains an unbound check. Display status is cached for five seconds and the combined inspection has a 12-second deadline. Each of the two sequential merge-validation inspections has a six-second deadline, keeping their combined validation budget below the 15-second serving request budget. A deadline aborts the underlying GitHub CLI process before releasing the shared in-flight request. An unreadable or incomplete rule source, a pending/missing/failing check, another open or merged PR for the issue, a conflict, or a closed PR blocks merging.
12
12
13
-
Automatic merging also requires strict required status checks as a server-enforced current-base policy. A client-side fetch cannot supply that guarantee. Merge-queue branches were blocked in this increment because `gh pr merge` can report a successful enqueue before the pull request is merged. Queue support has since landed (#24; see `merge-queue.md`): a queue-capable adapter tracks each attempt until GitHub confirms the merge, removal or failure, and queue rules count as the server-side current-base guard. An adapter without queue inspection still fails closed with a `merge-queue` blocker. The coordinator performs the complete gate twice without using the display cache, verifies the same base/head pair, then re-reads the local review generation immediately before invoking `gh pr merge --match-head-commit` with literal argv. The adapter invalidates its pre-action cache before the command and after either success or refusal. A generation guard prevents inspections started before or during the command from restoring stale cache entries afterward. After command success, the server returns “merge submitted” even if its best-effort status reload fails, and the browser keeps the action disabled until Refresh confirms GitHub state. GitHub's command refusal text is returned to the local UI, and a failed attempt also remains disabled until Refresh loads fresh state. There is no “merge anyway” path around missing atomic protection. Demo mode never constructs the coordinator, including when a gateway is injected.
13
+
Automatic merging also requires strict required status checks as a server-enforced current-base policy. A client-side fetch cannot supply that guarantee. Merge-queue branches were blocked in this increment because `gh pr merge` can report a successful enqueue before the pull request is merged. Queue support has since landed (#24; see `merge-queue.md`): a queue-capable adapter tracks each attempt until GitHub confirms the merge, removal or failure, and queue rules count as the server-side current-base guard. An adapter without queue inspection still fails closed with a `merge-queue` blocker. The coordinator performs the complete gate twice without using the display cache, verifies the same base/head pair, then re-reads the local review generation immediately before invoking `gh pr merge --match-head-commit` with literal argv. Each `gh` process gets only the allowlisted variables in `github/gh-env.ts`, plus fixed settings that turn off prompts, the pager, colour and update checks. No other server variables are passed on. A timeout or abort sends the `gh` process SIGTERM, then SIGKILL after a quarter of a second, and the call returns only after that process has exited (or 0.15 s after it exits, if a process it started keeps its output open). So a merge click aborted at its 14-second deadline settles by 14.4 s, inside the 14.5-second shutdown drain and below the 15-second request budget. Stopping `gh` cannot recall a merge request it had already sent: GitHub can still apply it, so a cancelled merge is reported as unknown and its attempt stays in flight, with the action disabled. A direct attempt is settled only when GitHub shows the pull request merged; a queued attempt also settles when GitHub reports it removed or failed (`merge-queue.md`). The adapter invalidates its pre-action cache before the command and after either success or refusal. A generation guard prevents inspections started before or during the command from restoring stale cache entries afterward. After command success, the server returns “merge submitted” even if its best-effort status reload fails, and the browser keeps the action disabled until Refresh confirms GitHub state. GitHub's command refusal text is returned to the local UI, and a failed attempt also remains disabled until Refresh loads fresh state. There is no “merge anyway” path around missing atomic protection. Demo mode never constructs the coordinator, including when a gateway is injected.
14
14
15
15
Shutdown sets a terminal admission flag before inspecting active work and checks it again after partial request bodies are received. It then stops HTTP admission, aborts and awaits an active merge CLI process, drains requests, and closes the review service. A request admitted before shutdown cannot start a new merge afterward, and a cancelled command cannot outlive the local state that authorized it.
thrownewError('A GitHub repository, pull request, and issue are required for merging.');
88
96
if(config.method!==undefined&&!['merge','squash','rebase'].includes(config.method))thrownewError('GitHub merge method must be merge, squash, or rebase.');
0 commit comments