Skip to content

fix(agent): Refresh egress token and retry on sandbox git auth failure#1426

Open
srtab wants to merge 1 commit into
mainfrom
claude/brave-morse-b3b5b3
Open

fix(agent): Refresh egress token and retry on sandbox git auth failure#1426
srtab wants to merge 1 commit into
mainfrom
claude/brave-morse-b3b5b3

Conversation

@srtab

@srtab srtab commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Problem

Sentry DAIV-1V (GitPushPermissionError): in sandbox-mode runs, in-git operations authenticate via a git-platform token that the egress proxy injects. That token is minted once at turn start with a fixed ~1h TTL (GitHub installation tokens). The publish push runs at turn end, so a turn that runs longer than the TTL — e.g. a dependency-bump task that runs the test suite repeatedly — fails to push with a 401 (could not read Username), and the agent's work is lost.

The existing _arefresh_egress only refreshes at turn start (on session reuse), which never covered a single turn outliving its own token. Local mode is immune (it fetches a fresh credential per publish); GitLab is immune (clone tokens live 3 days). This is GitHub-specific.

Fix

Reactive refresh-and-retry, wired sandbox-mode only:

  • GitManager gains an on_auth_failure callback. A new _run_sandbox() chokepoint (used by both single commands and batches) invokes it and retries once when an in-sandbox remote git op (ls-remote/push/fetch/pull) is rejected with an auth marker.
  • GitChangePublisher._refresh_sandbox_egress() is the callback: it re-mints the platform token and delivers it to the live session via SandboxFileBackend.refresh_egress()update_egress.
  • sandbox_envs.services.refresh_platform_egress() swaps only the platform secret value (no duplicate egress rule), and returns the config unchanged when the re-mint yields the same token (GitLab's day-cached case) so the one-shot retry isn't wasted.

The retry is deliberately narrow: scoped to remote ops (a local diff mentioning "permission denied" won't trigger it), gated on a non-zero exit, and bounded to one refresh per manager spanning the ls-remote → push sequence — so it never loops or masks a genuine permission failure. A distinct warning is logged when an op is still auth-rejected after a real refresh (→ branch protection / token scope, not expiry).

Why retry over a longer TTL

GitHub installation-token lifetime isn't configurable, and a longer-lived write token injected into every sandbox request is a security regression. Refreshing at the failing op decouples credential freshness from turn duration for a turn of any length.

Testing

  • New behavioral coverage for the retry state machine: retry-succeeds (ls-remote + push paths), retry-still-fails-then-raises (once-only), refresh-reports-failure (no retry), no-callback path, remote-op scoping, successful-op-with-auth-like-output ignored, budget shared across distinct ops, and the mint-side vs delivery-side failure paths.
  • refresh_platform_egress unit tests: token swap without duplicate rule, no-op on None / token-less / identical-token.
  • All affected suites pass (automation/agent, sandbox_envs, core/sandbox); make lint clean; make lint-typing introduces no new errors.

The sandbox's in-git operations authenticate via an egress-proxy token
minted at turn start with a fixed ~1h TTL (GitHub installation tokens).
On a turn that outlives the TTL — e.g. a dependency bump that runs the
test suite repeatedly — the turn-end publish push was rejected with a
401 and the agent's work was lost (Sentry DAIV-1V).

When an in-sandbox remote git op (ls-remote/push/fetch) is auth-rejected,
GitManager now invokes an injected callback that re-mints the platform
token onto the live session and retries the op once. Bounded to one
refresh per manager and scoped to remote ops on a non-zero exit, so it
never loops or masks a real permission failure. GitLab (3-day tokens)
and local mode are unaffected.
@srtab srtab self-assigned this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant