fix(strategy): bound the empty-remote probe so a stalled remote can't block git push · Entire

fix(strategy): bound the empty-remote probe so a stalled remote can't block git push

ba7e1a9→main·

karthik-rameshkumar·3d ago·1 file·+17 added/-2 removed

Addresses trail review on #1744: deferCheckpointPushOnEmptyRemote ran
PushTargetsInDir and LsRemoteInDir on the raw hook ctx, which carries no
deadline — unlike every other network call in this package. The probe runs
synchronously inside pre-push, before the user's actual git push starts, so
a stalled or unreachable remote would hang the entire push indefinitely on
every invocation until the bootstrap marker is set.

Wrap the probe (target resolution + per-target ls-remote) in a single
context.WithTimeout (pushBootstrapProbeTimeout, 10s — matching the package's
other small remote reads). A timeout flows into the existing fail-closed
paths: metadata publication defers while the user's push proceeds.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

Sessions

01KXFZR5NRJGC1BAXTMG8GP20HView transcript

Changes

1

193 unmodified lines

194
195
196
197
197
198
199
200
201
202
203
204
205
206
207
208
20 unmodified lines

229
230
231
224
232
233
234
235
40 unmodified lines

276
277
278
279
280
281
282
283
284
285
286
287
288

193 unmodified lines

return true
    }

targets, err := checkpointremote.PushTargetsInDir(ctx, dir, ps.remote)
// The hook ctx carries no deadline, and this probe runs synchronously before
// the user's actual git push starts — an unbounded ls-remote against a
// stalled remote would block the whole push. Bound the probe; a timeout
// flows into the fail-closed paths below (defer metadata, let the user's
// push proceed).
probeCtx, cancel := context.WithTimeout(ctx, pushBootstrapProbeTimeout)
defer cancel()

targets, err := checkpointremote.PushTargetsInDir(probeCtx, dir, ps.remote)
if err != nil {
    // Fail closed for checkpoint publication: the user's git push continues
    // normally, while a later push can publish the pending metadata once the
20 unmodified lines

}

for _, target := range targets {
    out, lsErr := checkpointremote.LsRemoteInDir(ctx, dir, target, "refs/heads/*")
out, lsErr := checkpointremote.LsRemoteInDir(probeCtx, dir, target, "refs/heads/*")
if lsErr != nil {
    // Fail closed for checkpoint publication: the user's git push continues
    // normally, while a later push can publish the pending metadata once the
40 unmodified lines

// recreated under the same URL could wrongly skip the guard.
const pushBootstrapTTL = time.Hour

// pushBootstrapProbeTimeout bounds the empty-remote probe (push-target
// resolution plus one ls-remote per target). The hook ctx has no deadline of
// its own, and the probe runs synchronously before the user's git push starts,
// so without a bound a stalled remote would block the push indefinitely.
// Matches the 10s used by the other small remote reads in this package.
const pushBootstrapProbeTimeout = 10 * time.Second

// pushBootstrapMarkerPath is the repo-level file recording that every resolved
// push target has been observed to carry at least one branch. It lives under
// the git common dir (shared across worktrees) rather than in .git/config so it

Mcmd/entire/cli/strategy/manual_commit_push.go+17/-2