With --github-repository-routing checkout, each command gets one repository-scoped installation token, chosen from the origin of the checkout its launch cwd resolves to (gitHubRepositoryForCommand in packages/code/src/github.ts, resolved in cli.ts before launch). Git then presents that token for every github.com URL through http.https://github.com/.extraheader. Any command that reaches a different repository after launch presents the wrong token and gets:
remote: Permission to <owner>/<repo>.git denied to <app>[bot].
fatal: unable to access 'https://github.com/<owner>/<repo>.git/': The requested URL returned error: 403
Agents do this routinely: cd ../other-repo && git push, git -C <other checkout> push, a clone of another repository nested inside the admitted checkout, a worktree of repo A stored under repo B's .worktrees/, or pushing a remote other than origin. The 403 names the bot, so it reads like a permission or expiry problem, and agents learn to retry, which never helps.
Evidence from one trusted-VM worker over 9 days: 49 of 257 agent pushes were denied. None were 401s or expired tokens. Every denial with a known launch context matched the patterns above (nested clones under the LibreChat checkout, cross-repo worktrees), or predated routing (a fixed installation that did not cover a transferred repository).
Related transfer hazard: installation lookup uses redirect: 'error' and caches the installation per owner/name string for the life of the process. A repository transferred to another owner keeps working under its old remote URL for git, but the worker's lookup for the old name fails hard, or reuses a stale installation.
Proposal
- Resolve credentials per request URL instead of per launch cwd. A git credential helper (or a per-URL
extraheader) that mints a token for the repository in the URL being fetched or pushed, and returns nothing for repositories outside the admitted set, would cover cd, git -C, nested clones and non-origin remotes. Mint lazily and cache per repository as today.
- For
gh, keep the launch-checkout token as the default, but let GH_REPO/--repo resolve their own token the same way if feasible.
- Follow a transfer: on a 301 from
/repos/{owner}/{name}/installation, re-resolve with the canonical full_name (and cache under both names) instead of failing.
- When a token for repository X is presented to Y, fail with an actionable message where the worker can detect it (for example, a credential helper request for an unadmitted repository), naming both repositories.
Acceptance
- A command launched in checkout A that pushes checkout B (sibling, nested, or via
git -C) succeeds when B is admitted and its installation covers it.
- A push to an unadmitted repository fails with a message naming the repository, not a bot-permission 403.
- A checkout whose
origin still uses a transferred repository's old owner authenticates correctly.
With
--github-repository-routing checkout, each command gets one repository-scoped installation token, chosen from theoriginof the checkout its launch cwd resolves to (gitHubRepositoryForCommandinpackages/code/src/github.ts, resolved incli.tsbefore launch). Git then presents that token for everygithub.comURL throughhttp.https://github.com/.extraheader. Any command that reaches a different repository after launch presents the wrong token and gets:Agents do this routinely:
cd ../other-repo && git push,git -C <other checkout> push, a clone of another repository nested inside the admitted checkout, a worktree of repo A stored under repo B's.worktrees/, or pushing a remote other thanorigin. The 403 names the bot, so it reads like a permission or expiry problem, and agents learn to retry, which never helps.Evidence from one trusted-VM worker over 9 days: 49 of 257 agent pushes were denied. None were 401s or expired tokens. Every denial with a known launch context matched the patterns above (nested clones under the LibreChat checkout, cross-repo worktrees), or predated routing (a fixed installation that did not cover a transferred repository).
Related transfer hazard: installation lookup uses
redirect: 'error'and caches the installation perowner/namestring for the life of the process. A repository transferred to another owner keeps working under its old remote URL for git, but the worker's lookup for the old name fails hard, or reuses a stale installation.Proposal
extraheader) that mints a token for the repository in the URL being fetched or pushed, and returns nothing for repositories outside the admitted set, would covercd,git -C, nested clones and non-originremotes. Mint lazily and cache per repository as today.gh, keep the launch-checkout token as the default, but letGH_REPO/--reporesolve their own token the same way if feasible./repos/{owner}/{name}/installation, re-resolve with the canonicalfull_name(and cache under both names) instead of failing.Acceptance
git -C) succeeds when B is admitted and its installation covers it.originstill uses a transferred repository's old owner authenticates correctly.