Skip to content

Commit 5df0a67

Browse files
endolinbotclaude
andcommitted
ensure-project-worktree: refuse a silently stale tree when the branch fetch fails
The branch fetch swallowed every failure (2>/dev/null || true), so a transient network/auth blip silently left refs/heads/$branch at a stale local SHA and the gardener worked an old tree with no warning. Observed 2026-07-06: endo-but-for-bots@llm delivered 8 weeks stale at 68246ad9 (missing the docs the job named) while origin/llm was at 11322892. After the fetch, verify the local head against the authoritative remote tip (git ls-remote), retry once on divergence, then die rather than hand back a stale tree. The remote lookup gets one retry past a blip too. The legitimate no-op case (a detached ref/sha on neither side) is preserved. Adds a hermetic regression to project-worktree-isolation-test: advance the upstream tip but delete its objects so ls-remote still advertises it while the fetch fails, and assert the helper refuses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8aab4e4 commit 5df0a67

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

scripts/jobs/ensure-project-worktree.sh

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,49 @@ mkdir -p "$GARDEN_SCRATCH"
114114
# <branch>`; fetching it explicitly into refs/heads/ makes the name resolve. The
115115
# `+` forces an update if the local head already exists. If the branch is on
116116
# neither side the fetch is a no-op and the add below surfaces a clear error.
117-
git --git-dir="$bare" fetch --quiet origin \
118-
"+refs/heads/${branch}:refs/heads/${branch}" 2>/dev/null || true
117+
#
118+
# ── Silent stale-fetch guard (the 2026-07-06 regression) ─────────────────────
119+
# A bare `2>/dev/null || true` on the fetch swallows EVERY failure, so a transient
120+
# network/auth blip silently leaves refs/heads/$branch at a STALE local SHA and
121+
# the gardener works an old tree with no warning. Observed 2026-07-06 (job
122+
# design-daemon-agent-tools-reconcile-mount-git-capabilities): endo-but-for-bots@llm
123+
# was delivered 8 weeks stale at 68246ad9 — missing the very docs the job named —
124+
# while origin/llm was at 11322892, and a manual `git fetch origin llm` succeeded
125+
# moments later. So we verify the local head against the AUTHORITATIVE remote tip
126+
# (`git ls-remote`) after the fetch, retry once on any divergence, and die rather
127+
# than hand back a stale tree. The remote lookup itself gets one retry so a blip
128+
# there does not defeat the guard.
129+
remote_branch_sha() {
130+
git --git-dir="$bare" ls-remote origin "refs/heads/${branch}" 2>/dev/null | cut -f1
131+
}
132+
local_branch_sha() {
133+
git --git-dir="$bare" rev-parse --verify --quiet "refs/heads/${branch}" 2>/dev/null || true
134+
}
135+
fetch_branch() {
136+
git --git-dir="$bare" fetch --quiet origin \
137+
"+refs/heads/${branch}:refs/heads/${branch}" 2>/dev/null
138+
}
139+
140+
remote_sha="$(remote_branch_sha)"
141+
[ -z "$remote_sha" ] && remote_sha="$(remote_branch_sha)" # one retry past a blip
142+
143+
fetch_branch || true
144+
145+
if [ -n "$remote_sha" ]; then
146+
# The branch exists upstream: the local head MUST equal the remote tip, else the
147+
# fetch silently failed or delivered a stale ref. Retry once, then refuse.
148+
have_sha="$(local_branch_sha)"
149+
if [ "$have_sha" != "$remote_sha" ]; then
150+
log "WARN: ensure-project-worktree: refs/heads/${branch} is ${have_sha:-<absent>} but origin/${branch} is ${remote_sha}; retrying fetch"
151+
fetch_branch || true
152+
fi
153+
now_sha="$(local_branch_sha)"
154+
if [ "$now_sha" != "$remote_sha" ]; then
155+
die "ensure-project-worktree: could not fetch ${repo}@${branch} to ${remote_sha} (local head is ${now_sha:-absent}, likely a transient network/auth failure); refusing to hand back a stale tree"
156+
fi
157+
fi
158+
# else: the branch is on neither side (a legitimate detached ref/sha checkout) —
159+
# the fetch is a no-op and the add below resolves $ref locally or errors clearly.
119160

120161
git --git-dir="$bare" worktree add --detach "$wt" "$ref" >/dev/null \
121162
|| die "ensure-project-worktree: could not check out $repo@$ref into $wt"

scripts/jobs/test/project-worktree-isolation-test.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,38 @@ P3="$(run_helper garden-fix-error-trace endojs/endo main)"
128128
[ "$P3" != "$P1" ] && ok "same base, different repo → DISTINCT path (no self-collision)" \
129129
|| bad "same base collided across repos ('$P3' == '$P1')"
130130

131+
# === 6: silent stale-fetch guard — remote advances but the fetch can't deliver =
132+
# Regression for the 2026-07-06 stale-tree delivery: when origin advertises a NEW
133+
# tip (ls-remote succeeds) but the fetch of it fails (a transient blip), the
134+
# helper must REFUSE rather than silently hand back the stale local ref.
135+
make_fork endojs stale-guard main
136+
UP="$TR/upstream-endojs-stale-guard.git"
137+
SEED="$TR/seed-endojs-stale-guard"
138+
SG_BARE="$GROOT/worktrees/endojs-stale-guard.git"
139+
git -C "$UP" config gc.auto 0 >/dev/null 2>&1; git -C "$UP" config receive.autogc false >/dev/null 2>&1
140+
# establish a fresh baseline (bare refs/heads/main = T1) via one good run
141+
run_helper garden-stale-baseline endojs/stale-guard main >/dev/null
142+
# advance upstream to T2, then delete T2's objects so ls-remote still advertises
143+
# the new tip but a fetch of it fails — the exact transient shape we must catch.
144+
find "$UP/objects" -type f | sort > "$TR/obj-before"
145+
( cd "$SEED"
146+
printf 'upstream T2 — must-not-be-skipped\n' > error-trace.js
147+
git "${git_id[@]}" commit -qam T2
148+
git push -q origin main ) >/dev/null 2>&1
149+
find "$UP/objects" -type f | sort > "$TR/obj-after"
150+
comm -13 "$TR/obj-before" "$TR/obj-after" | while read -r f; do rm -f "$f"; done
151+
# sanity: ls-remote still advertises the (now unfetchable) T2 tip
152+
adv="$(git --git-dir="$SG_BARE" ls-remote origin refs/heads/main 2>/dev/null | cut -f1)"
153+
seed_t2="$(git -C "$SEED" rev-parse HEAD)"
154+
[ -n "$adv" ] && [ "$adv" = "$seed_t2" ] && ok "ls-remote advertises the advanced (unfetchable) tip" \
155+
|| bad "test setup: ls-remote did not advertise T2 (adv='$adv' t2='$seed_t2')"
156+
# the guard must make the helper DIE, not emit a stale-tree path
157+
if out="$(run_helper garden-stale-victim endojs/stale-guard main 2>/dev/null)"; then
158+
bad "helper handed back a tree despite an undeliverable remote tip (stale!): '$out'"
159+
else
160+
ok "helper REFUSES (dies) rather than delivering a stale tree"
161+
fi
162+
131163
# --- summary -----------------------------------------------------------------
132164
echo "----------------------------------------------------------------"
133165
echo "project-worktree-isolation: $PASS passed, $FAIL failed"

0 commit comments

Comments
 (0)