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
Follow-up to #676, which shipped the detection half in 7d246958. That change halts a run after a wrong-repo write and names the repository. It does not stop the write. This issue is the half that does.
The problem, measured
The Engine is a claude --dangerously-skip-permissions child process on the owner's machine, spawned at packages/browser-runner/src/coding/headless.ts:420 with env: mergeEnv(process.env, this.config.env) — the machine env inherited wholesale. It therefore reaches GitHub with the account's credentials:
git push is over SSH. Both remotes are git@github-personal:… (ProAgentStore/platform and proappstore-online/platform). Git ignores userinfo on an ssh URL, so no token of any kind can gate these pushes.
gh is authed from the keyring, account serge-ivo, scopes admin:org, delete_repo, gist, repo, workflow — every org the account can see.
Nothing injects a GitHub credential today.grep -rn "GH_TOKEN\|GITHUB_TOKEN" packages/browser-runner/src workers/api/src returns only redaction logic. grep -rni "credential\.helper\|GIT_CONFIG_COUNT\|ASKPASS\|insteadOf\|GIT_SSH_COMMAND" returns 0.
The Engine steers its own credential. Observed on two runs: export GH_CONFIG_DIR=~/.config/gh-personal; gh pr merge 111 --merge --repo proappstore-online/platform. Anything env-based is therefore a speed bump, not a boundary, unless the engine is also denied the ability to re-point it.
The tension that has to be designed around, not through
#676 item 2 wants writes scoped; item 3 wants reads broad, and gives a real example. A process holds exactly one gh token, so:
scoped GH_TOKEN → gh pr create -R other/repo fails at the credential (good) andgh pr view 138 --repo proappstore-online/platform fails too (breaks the protected pattern);
broad read-only GH_TOKEN → reads fine, but the engine cannot open a PR on its own registered repo.
Verified this is not hypothetical: the cross-repo read is the Engine running gh, not a platform tool. Instance a185b1db ("PAGS Coder"), session csess_42bdfe20, 2026-08-16T01:38:04Z, the Pilot's own instruction: Please run: `gh pr view 138 --repo proappstore-online/platform --json tit…` . The platform's github_read_pull was never called on 138 from that instance.
So the answer is a per-invocation credential chooser, not a single injected token.
Sketch
A runner-owned bin directory prepended to the Engine's PATH, holding a gh shim that classifies argv and routes the credential:
read verb → real gh with a broad read-only token, or the machine's own auth.
Plus, for git: GIT_CONFIG_* env injecting a credential helper, and url.<https>.insteadOf rules to force github SSH remotes through it.
Risks this carries, stated up front
Forcing SSH → https means taking over the machine's git configuration. The owner uses a custom github-personal host alias; rewriting it affects their own reads, not just the agent's. This is the single biggest design risk and may be a reason to scope this issue to gh only and accept that git push stays ungated.
gh's argv surface is large. Misclassifying a write as a read is a hole; default-denying unknown subcommands breaks reads. The classification needs to be a closed, reviewed vocabulary — the same shape as packages/browser-runner/src/coding/repo-write.ts.
A shim on PATH is bypassable by absolute path (/opt/homebrew/bin/gh) or by the engine re-exporting its own env, as it already does. This raises the cost of a wrong-repo write; it does not make one impossible. Anything claiming otherwise in the UI would be worse than nothing.
A write to an unregistered repo through gh fails at the credential layer, with the refused repo in stderr, and the run reports that rather than a generic failure.
A cross-repo gh pr view still succeeds — pinned by a test, because it is the property most likely to be lost.
Follow-up to #676, which shipped the detection half in
7d246958. That change halts a run after a wrong-repo write and names the repository. It does not stop the write. This issue is the half that does.The problem, measured
The Engine is a
claude --dangerously-skip-permissionschild process on the owner's machine, spawned atpackages/browser-runner/src/coding/headless.ts:420withenv: mergeEnv(process.env, this.config.env)— the machine env inherited wholesale. It therefore reaches GitHub with the account's credentials:git pushis over SSH. Both remotes aregit@github-personal:…(ProAgentStore/platformandproappstore-online/platform). Git ignores userinfo on an ssh URL, so no token of any kind can gate these pushes.ghis authed from the keyring, accountserge-ivo, scopesadmin:org, delete_repo, gist, repo, workflow— every org the account can see.grep -rn "GH_TOKEN\|GITHUB_TOKEN" packages/browser-runner/src workers/api/srcreturns only redaction logic.grep -rni "credential\.helper\|GIT_CONFIG_COUNT\|ASKPASS\|insteadOf\|GIT_SSH_COMMAND"returns 0.export GH_CONFIG_DIR=~/.config/gh-personal; gh pr merge 111 --merge --repo proappstore-online/platform. Anything env-based is therefore a speed bump, not a boundary, unless the engine is also denied the ability to re-point it.The tension that has to be designed around, not through
#676 item 2 wants writes scoped; item 3 wants reads broad, and gives a real example. A process holds exactly one
ghtoken, so:GH_TOKEN→gh pr create -R other/repofails at the credential (good) andgh pr view 138 --repo proappstore-online/platformfails too (breaks the protected pattern);GH_TOKEN→ reads fine, but the engine cannot open a PR on its own registered repo.Verified this is not hypothetical: the cross-repo read is the Engine running
gh, not a platform tool. Instancea185b1db("PAGS Coder"), sessioncsess_42bdfe20,2026-08-16T01:38:04Z, the Pilot's own instruction:Please run: `gh pr view 138 --repo proappstore-online/platform --json tit…`. The platform'sgithub_read_pullwas never called on 138 from that instance.So the answer is a per-invocation credential chooser, not a single injected token.
Sketch
A runner-owned bin directory prepended to the Engine's
PATH, holding aghshim that classifies argv and routes the credential:ghwith a repo-scoped installation token (repoScopedInstallationTokenalready exists, added in A coding instance can push to and open PRs on any repo the user can reach, not just its registered one #676);ghwith a broad read-only token, or the machine's own auth.Plus, for git:
GIT_CONFIG_*env injecting a credential helper, andurl.<https>.insteadOfrules to force github SSH remotes through it.Risks this carries, stated up front
github-personalhost alias; rewriting it affects their own reads, not just the agent's. This is the single biggest design risk and may be a reason to scope this issue toghonly and accept thatgit pushstays ungated.gh's argv surface is large. Misclassifying a write as a read is a hole; default-denying unknown subcommands breaks reads. The classification needs to be a closed, reviewed vocabulary — the same shape aspackages/browser-runner/src/coding/repo-write.ts.PATHis bypassable by absolute path (/opt/homebrew/bin/gh) or by the engine re-exporting its own env, as it already does. This raises the cost of a wrong-repo write; it does not make one impossible. Anything claiming otherwise in the UI would be worse than nothing.PATHshim applies to every engine — so unlike A coding instance can push to and open PRs on any repo the user can reach, not just its registered one #676's gate, this one would work on Codex/Grok too. That is a point in its favour.Acceptance
ghfails at the credential layer, with the refused repo in stderr, and the run reports that rather than a generic failure.gh pr viewstill succeeds — pinned by a test, because it is the property most likely to be lost.coding_diagnosticsalongsidegithubApp.writeScope, replacing theenforcement: "acts-observed-halt"value A coding instance can push to and open PRs on any repo the user can reach, not just its registered one #676 shipped.