Repository navigation
Description
This was generated by AI during triage.
Problem
A single transient network failure in a read-only git command fails a whole Pass and sends a failure notification. On 2026-10-19 at 22:06 EDT, the scheduled Architect run (--plan-only) for Educational-Travel-Adventures/tourbot on dev.etadventures.com failed 7 seconds in, while selecting an Issue branch for #880:
Result: review failed
Review: failed
Cause: git ls-remote --heads origin refs/heads/issue-880 refs/heads/issue-880-branch-* failed: error: RPC failed; curl 16 Error in the HTTP2 framing layer
fatal: expected flush after ref listing
Built with claude · default model · default effort (configured)
Host: dev.etadventures.com
Took: 7s
The same ls-remote succeeded in 0.6s the next morning. The cron logs on that host show one more failure of the same kind (git ls-remote --heads origin refs/heads/master failed: ... Error in the HTTP2 framing layer). The host's git uses curl 7.76.1 with nghttp2 1.43.
#786 added bounded retries for transient gh reads (via check_read::is_transient), but git commands that talk to origin are not covered. The Git wrapper's retry loop retries only when a git lock is held (held_lock), so a transport failure is final on its first attempt.
Agent Brief
Category: enhancement
Summary: Retry transient transport failures of read-only git network operations (ls-remote, fetch) within a small bounded budget before failing a Pass.
Current behavior:
Read-only git commands against origin run through the Git wrapper. Its retry loop retries only when another process holds a git lock. Any other nonzero exit, including a dropped HTTP/2 stream, a connection reset or a GitHub 5xx, becomes the Pass's failure cause right away. Callers include Issue branch selection (the Reads::on_origin read in branch selection), Pickup's branch reservation check, Worktree ownership checks and the Git wrapper's remote-head probe, as well as fetches.
Desired behavior:
- A read-only git network operation (
ls-remote,fetch) that fails with a transient transport classification is retried with increasing delay within a small bounded budget (for example 3 attempts, well under a minute in total). If every attempt fails, the operation fails with the last attempt's cause, in the same shape as today. - The transient classification recognises git/curl transport failures, for example:
RPC failed; curl <n>(e.g. 16, 18, 56),Error in the HTTP2 framing layer,expected flush after ref listing,early EOF,the remote end hung up unexpectedly,Connection reset,Connection timed out,Could not resolve host,The requested URL returned error: 5xx(and 429), and TLS read errors (SSL_read,gnutls_handshake). - Permanent failures fail on the first attempt: authentication failures (
Authentication failed,could not read Username, 401/403),Repository not found, unknown refs or remotes, and any error not classified as transient. - Each retry writes a progress/log line naming the command and the cause, so recovered failures stay visible.
- Command interruption cancels a pending retry wait promptly.
- The existing lock-wait behavior is unchanged.
Key interfaces:
- The
Gitwrapper's retried-output path: it gains a transient-transport retry for read-only network commands, alongside the existingheld_lockretry. - A transport-failure classifier analogous to
check_read::is_transient, matching on git's stderr.
Out of scope:
pushand any other mutation of origin. Pushes go throughrun_transitionwith outcome checks, and a blind retry could double-apply them. If a push should retry, that is a separate issue.ghreads, already covered by Retry transient GitHub read failures before failing a Pass #786.- GitHub quota exhaustion and deferral (Defer scheduled passes during GitHub quota exhaustion and reduce idle polling #739).
- Harness/model service failures (Recover from temporary Harness service failures within a bounded retry budget #584).
- Forcing HTTP/1.1 or otherwise changing the user's git configuration.
Acceptance criteria
- With a fake git that fails
ls-remoteonce witherror: RPC failed; curl 16 Error in the HTTP2 framing layer/fatal: expected flush after ref listingand then succeeds, Issue branch selection returns the second attempt's result, and the Pass continues with no failure notification. - A fake
fetchthat fails once with a transient transport error and then succeeds is retried the same way. - A transient failure that persists through the whole budget fails within the budget, with the last attempt's git error as the cause.
- Authentication failures,
Repository not foundand other non-transient errors fail on the first attempt, with no retry. -
pushis never retried by this mechanism. A test shows a push transport failure is attempted exactly once. - Each retry emits one log line naming the git command and the cause.
- Interrupting the Command during a retry wait ends the wait promptly.
- Existing lock-wait retry tests still pass.
Related
- Retry transient GitHub read failures before failing a Pass #786: transient
ghread retries (same pattern, GitHub API side). - Retry transient CI check reads within a bounded interruptible budget #731: transient CI check read retries.
- Defer scheduled passes during GitHub quota exhaustion and reduce idle polling #739: quota-aware deferral.
- Recover from temporary Harness service failures within a bounded retry budget #584: transient Harness service failures.
Activity