fabro/lib
thewoolleyman 7e97fba7f6 feat(workflow): keep push credentials fresh across long ACP turns
GitHub App installation tokens expire ~60 min after minting. On a long run,
the origin token baked into the sandbox clone at clone time is stale by the
time a late ACP node (e.g. the PR node) runs `git push`, causing an
`Invalid username or token` failure.

Two host-driven mechanisms, both using the existing
`Sandbox::refresh_push_credentials()` (re-mint + `git remote set-url origin`)
over the exec channel — no new inbound surface:

1. Turn-entry re-mint at each ACP node entry, so a push early in the turn uses
   a fresh token.
2. A background refresh-ahead loop, scoped to the turn via a drop-guard, that
   re-mints every 45 min so a single push-bearing turn that itself exceeds the
   TTL stays fresh. A normal sub-interval turn never ticks; a failed/timed-out
   tick retries sooner so a transient error cannot leave a longer-than-interval
   expired-token window.

Both refresh calls are timeout-bounded (30s) so a stalled GitHub API cannot
hang node entry. FABRO_PUSH_CRED_REFRESH_AHEAD (default on; falsy = empty/0/
false/off/no, case-insensitive) disables the whole feature — turn-entry and
loop — for operators who manage `origin` themselves;
FABRO_PUSH_CRED_REFRESH_INTERVAL_SECONDS overrides the interval (0 disables
just the loop). Both are added to the worker env allowlist.

refresh_push_credentials now returns RefreshOutcome (Refreshed vs Skipped) so
callers log accurately: Refreshed only when a GitHub App installation token was
actually re-minted; a static PAT or pre-minted Installation token (nothing to
re-mint) short-circuits to Skipped before the set-url exec.

Known follow-ups documented in-code: (a) resumed runs reconnect without App
creds, so refresh no-ops until they are threaded through the reconnect path;
(b) no freshness check on the per-entry mint; (c) the background set-url can
contend with the agent's own git on .git/config.lock; (d) parallel ACP branches
each run their own loop; (e) the refresh lives in the ACP handler only though
the stale-origin problem is stage-agnostic (native/command stages are not
covered); (f) refresh failures are logged via tracing but not surfaced as a
RunNotice event.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 02:47:31 +02:00
..
crates feat(workflow): keep push credentials fresh across long ACP turns 2026-07-11 02:47:31 +02:00
packages/fabro-api-client Resolve run.prepare.steps env and interpolation at the run boundary (#530) 2026-07-01 10:31:26 -04:00