Give the merge and issue gh runners the allowlisted environment - #86
Merged
Merged
Conversation
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation consistently applies the shared environment policy and includes focused regression coverage.
Review effort: Balanced
Findings: None
What changed in this PR
This PR restricts merge and issue GitHub CLI subprocesses to the shared allowlisted environment.
Changes:
- Applies
ghEnvironment()to merge and issue runners. - Adds regression coverage for all four GitHub adapters.
- Documents subprocess environment isolation.
| File | Description |
|---|---|
github/merge.ts |
Applies the allowlisted environment to merge commands. |
github/issues.ts |
Applies the allowlisted environment to issue commands. |
test/gh-env.test.ts |
Verifies allowed and unrelated environment variables. |
docs/implementation/guarded-merge.md |
Documents merge subprocess isolation. |
docs/implementation/issue-prioritization.md |
Documents issue subprocess isolation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mchwang
force-pushed
the
feat/gh-env-merge-issues
branch
from
September 29, 2026 21:09
7019d51 to
b33eb2a
Compare
mchwang
force-pushed
the
feat/gh-env-merge-issues
branch
12 times, most recently
from
September 30, 2026 06:56
55da8fb to
1aebc6d
Compare
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>
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>
#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>
mchwang
force-pushed
the
feat/gh-env-merge-issues
branch
from
October 1, 2026 08:03
1aebc6d to
d128cbc
Compare
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>
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>
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>
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>
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>
The queue watermark defaults to 6 s; 12 s is only its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang
marked this pull request as ready for review
October 1, 2026 16:45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #84. Merge #84 first. After that, this PR's base changes to
main.What this changes
The merge adapter (
GhMergeGateway) and the issue adapter (GhIssueGateway) ranghwith the whole server environment, including unrelated credentials. Their default runners now passenv: ghEnvironment()fromgithub/gh-env.ts(added in #84).With this change, every place that runs
ghuses the allowlisted environment:github/merge.tsgithub/issues.tsgithub/pull-requests.ts(throughgithub/run-with-input.ts)github/already-fixed.ts(throughgithub/run-with-input.ts)Test
test/gh-env.test.tsruns each adapter's own default runner against a fakeghthat prints its environment. For each adapter it checks:ghgh:GH_PROMPT_DISABLED,GH_NO_UPDATE_NOTIFIER,GH_PAGER,NO_COLOR. The test takes their values fromghEnvironment({}), so it follows the platform-specificGH_PAGERadded in F2d: already-fixed check and opening the task's PR #84.ghEvidence that the test catches failures:
merge.tsandissues.ts, the merge and issue cases fail.PATH, the merge case fails.Docs
docs/implementation/guarded-merge.mdanddocs/implementation/issue-prioritization.mdnow say what environmentghgets.Validation
These results are for head
1aebc6d, rebased onto #84 at6c15c41:npm run typecheck: passes.ghenvironment test files: 273 tests pass. The pull-request and already-fixed cases test F2d: already-fixed check and opening the task's PR #84'srunWithInputrunner.Earlier heads:
55da8fb, on F2d: already-fixed check and opening the task's PR #84 ataf56100: 895 tests passed, and this PR's CI passed.64bd56f, on F2d: already-fixed check and opening the task's PR #84 at8a4e5ea: 894 tests passed.8bc7b7e, on F2d: already-fixed check and opening the task's PR #84 atf094853: 891 tests passed, and this PR's CI passed.adb27ef, on F2d: already-fixed check and opening the task's PR #84 at46bbeba: 889 tests passed.56227f1, on F2d: already-fixed check and opening the task's PR #84 atfa037c9(which includesmain): 885 tests passed, and this PR's CI passed.2ec2615, on F2d: already-fixed check and opening the task's PR #84 atf5859f4: 833 tests passed.148425d, on F2d: already-fixed check and opening the task's PR #84 at0be7147: 826 tests passed, and this PR's CI passed.4b3349f, on F2d: already-fixed check and opening the task's PR #84 atdcc2b0c: 823 tests passed, and this PR's CI passed.4cff07a, on F2d: already-fixed check and opening the task's PR #84 atb32a330: 817 tests passed.4e6af26, on F2d: already-fixed check and opening the task's PR #84 atfc5887b: 816 tests passed.7019d51, on F2d: already-fixed check and opening the task's PR #84 at0f750f1: 800 tests passed.b33eb2a, on F2d: already-fixed check and opening the task's PR #84 at6b26e65: 804 tests passed.d835ce8, on F2d: already-fixed check and opening the task's PR #84 at9980445: 811 tests passed. In the first full run, one test intest/review.test.tsfailed. It passed 3 of 3 runs alone and passed in the full rerun, so it looks flaky under load.main's #83 (history Git with an allowlisted environment) changes onlygit/history.tsand its test, so it does not overlap with this PR.Independent review
A separate review agent, which did not share the author's context, reviewed this PR in 2 rounds.
Round 1 found no bugs. It confirmed that no other code runs
gh. It also confirmed that the allowlist covers whatgh pr merge --repoandgh apineed: sign-in through tokens, the config directory and the keyring, plus proxy and CA settings.Round 1 findings:
gh.ghalso gets the fixed settings.PATHcorrectly when it was unset.ghcall that does not go throughrun.ghcall must go throughrun.ghEnvironment()unit tests out ofpublish.test.ts./tmpcannot run programs.DISPLAYand Windows variables are not allowlisted.Round 2 reread the full diff and found nothing new. It checked that the test's environment changes cannot leak into other test files and are restored if setup fails partway.
No
AGENTS.mdchanges are needed: each finding is covered by an existing rule or is a one-off.🤖 Generated with Claude Code