Skip to content

Commit ca09007

Browse files
endolinbotclaude
andcommitted
journal-worktree-keeper: repair a missing origin from the canonical journal remote
The keeper's origin self-heal (jw_ensure_origin) re-added a vanished remote.origin.url only from $GARDEN_ROOT's origin. Derive it instead from journal_remote — the same canonical resolver every other consumer uses — whose fallback order is $JOURNAL_REMOTE -> the worktree origin -> the persisted per-host cache ($JOURNAL_REMOTE_CACHE, the companion job's last-good value) -> $GARDEN_ROOT's origin. The prior root-only source missed both an explicit $JOURNAL_REMOTE override and the cache, so it could not recover in the window where the root origin was momentarily gone too but the cache still held the last-good URL (the 2026-07-03 15:50-15:51Z "no origin" cascade). A possible journal_remote die() is confined to the command substitution so a genuinely unresolvable remote leaves the repair to the caller's own gate rather than killing the keeper. Adds test coverage for the missing-origin path (previously untested): origin re-added from an explicit $JOURNAL_REMOTE and from the persisted cache, each logging REPAIRED and reconciling without a maintainer page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aa1eb55 commit ca09007

2 files changed

Lines changed: 61 additions & 15 deletions

File tree

scripts/jobs/journal-worktree-keeper.sh

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -305,33 +305,43 @@ jw_repair_gitdir() { # jw_repair_gitdir <jw>
305305
}
306306

307307
# --- origin self-heal --------------------------------------------------------
308-
# Re-add a MISSING remote.origin.url on the worktree from the owning root's
309-
# origin, the same repo/remote the worktree tracks. This closes the transient
310-
# window where a one-worktree config gap — $JW loses remote.origin.url while
311-
# $GARDEN_ROOT keeps it — otherwise sends every git op that resolves the journal
308+
# Re-add a MISSING remote.origin.url on the worktree from the canonical journal
309+
# remote. This closes the transient window where a one-worktree config gap — $JW
310+
# loses remote.origin.url — otherwise sends every git op that resolves the journal
312311
# remote down the "no origin" fatal path, the 2026-07-03 15:50-15:51Z cascade that
313312
# FATAL-stormed every gardener/monitor/ci-watcher on claim at once. The keeper
314313
# already self-heals the worktree's BRANCH (fast-forward / diverged reset); this
315314
# heals its REMOTE the same way. Only acts when the worktree repo opens fine
316315
# (gitdir resolves — a pure config gap, NOT a dangling gitdir, which is
317-
# jw_repair_gitdir's job), origin is actually absent on $jw, and $GARDEN_ROOT
318-
# itself has an origin to copy. Idempotent + best-effort; a still-missing origin
319-
# falls through to the caller's own gate.
316+
# jw_repair_gitdir's job) and origin is actually absent on $jw.
317+
#
318+
# The URL comes from journal_remote (common.sh), the SAME canonical resolver every
319+
# other consumer uses, whose fallback order is: an explicit $JOURNAL_REMOTE ->
320+
# the worktree's own origin -> the persisted per-host cache ($JOURNAL_REMOTE_CACHE,
321+
# the companion job's last-good value, which survives a reset/deploy) -> the owning
322+
# $GARDEN_ROOT's origin. Reaching only into $GARDEN_ROOT (the prior implementation)
323+
# missed both an explicit $JOURNAL_REMOTE override AND the cache, so it could not
324+
# recover in the window where the root origin was momentarily gone too but the cache
325+
# still held the last-good URL. journal_remote may die() when nothing resolves at
326+
# all; the command substitution + `|| true` confine that exit to the subshell (and
327+
# 2>/dev/null suppresses its FATAL log), so a genuinely unresolvable remote leaves
328+
# the repair to the caller's own gate rather than killing the keeper.
329+
# Idempotent + best-effort.
320330
jw_ensure_origin() { # jw_ensure_origin <jw>
321-
local jw="$1" root_url
331+
local jw="$1" url
322332
# Only a valid, openable repo missing ONLY its origin is our case: a broken
323333
# gitdir is jw_repair_gitdir's, and a present origin is a no-op.
324334
git -C "$jw" rev-parse --git-dir >/dev/null 2>&1 || return 0
325335
git -C "$jw" config --get remote.origin.url >/dev/null 2>&1 && return 0
326-
# The owning root shares the same repo/remote as the journal worktree, so its
327-
# origin URL is the correct value to restore.
328-
root_url="$(git -C "$GARDEN_ROOT" config --get remote.origin.url 2>/dev/null || true)"
329-
[ -n "$root_url" ] || return 0
336+
# Canonical resolution: $JOURNAL_REMOTE -> worktree origin -> persisted cache ->
337+
# $GARDEN_ROOT origin (see journal_remote). Confine a possible die() to the subshell.
338+
url="$(journal_remote 2>/dev/null || true)"
339+
[ -n "$url" ] || return 0
330340
# `remote add` when the remote is wholly absent; fall back to setting the url
331341
# directly when an origin section exists without a url.
332-
if git -C "$jw" remote add origin "$root_url" >/dev/null 2>&1 \
333-
|| git -C "$jw" config remote.origin.url "$root_url" >/dev/null 2>&1; then
334-
log "REPAIRED: re-added missing remote.origin.url on $jw from \$GARDEN_ROOT origin ($root_url)"
342+
if git -C "$jw" remote add origin "$url" >/dev/null 2>&1 \
343+
|| git -C "$jw" config remote.origin.url "$url" >/dev/null 2>&1; then
344+
log "REPAIRED: re-added missing remote.origin.url on $jw from the canonical journal remote ($url)"
335345
fi
336346
return 0
337347
}

scripts/jobs/test/journal-worktree-keeper-test.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,42 @@ run_keeper
126126
grep -qF "fast-forwarded" <<<"$OUT" && ok "logged the fast-forward" || bad "did not log the fast-forward"
127127
[ "$(alert_count)" -eq 0 ] && ok "no alert on a clean fast-forward" || bad "alerted on a clean fast-forward"
128128

129+
# ============================================================================
130+
hr; echo "MISSING ORIGIN (JOURNAL_REMOTE) — origin re-added, worktree reconciled"; hr
131+
# The 2026-07-03 15:50-15:51Z cascade: the journal worktree opens fine (gitdir
132+
# resolves) but remote.origin.url momentarily vanishes, so journal_fetch — and every
133+
# other git op resolving the journal remote — dives down the "no origin" fatal path.
134+
# The keeper must re-add origin (here from an explicit $JOURNAL_REMOTE) BEFORE the
135+
# fetch, then reconcile normally, never leaving the root cause for a later tick.
136+
setup_fixture; write_alert_stub
137+
upstream_commit b c2 # upstream advances (so a real fetch matters)
138+
git -C "$JW" remote remove origin # drop origin — the exact wedge condition
139+
git -C "$JW" config --get remote.origin.url >/dev/null 2>&1 \
140+
&& bad "fixture: origin should be absent pre-run" \
141+
|| ok "fixture: remote.origin.url absent before the keeper runs"
142+
run_keeper JOURNAL_REMOTE="$UP"
143+
[ "$RC" -eq 0 ] && ok "exit 0 after re-adding a missing origin" || bad "exit $RC on missing origin"
144+
[ "$(git -C "$JW" config --get remote.origin.url 2>/dev/null)" = "$UP" ] \
145+
&& ok "origin re-added to the canonical remote" || bad "origin not re-added"
146+
grep -qF "REPAIRED:" <<<"$OUT" && ok "logged a REPAIRED: line for the re-added origin" || bad "did not log the origin repair"
147+
[ "$(head_sha)" = "$(remote_sha)" ] && ok "worktree reconciled to origin/journal2 after the repair" || bad "worktree not reconciled after re-adding origin"
148+
[ "$(alert_count)" -eq 0 ] && ok "no maintainer page on a self-healed origin" || bad "paged despite a self-healed origin"
149+
150+
# ============================================================================
151+
hr; echo "MISSING ORIGIN (persisted cache) — origin re-added from the companion cache"; hr
152+
# The companion job persists the last-good journal remote to $JOURNAL_REMOTE_CACHE
153+
# ($GARDEN_STATE/config/journal-remote). With NO $JOURNAL_REMOTE set and no root
154+
# origin, the keeper must still recover origin from that persisted canonical URL.
155+
setup_fixture; write_alert_stub
156+
git -C "$JW" remote remove origin
157+
mkdir -p "$TR/state/config"; printf '%s\n' "$UP" > "$TR/state/config/journal-remote"
158+
run_keeper # no JOURNAL_REMOTE; forces the cache path
159+
[ "$RC" -eq 0 ] && ok "exit 0 re-adding origin from the cache" || bad "exit $RC on cache-based origin repair"
160+
[ "$(git -C "$JW" config --get remote.origin.url 2>/dev/null)" = "$UP" ] \
161+
&& ok "origin re-added from the persisted cache" || bad "origin not re-added from the cache"
162+
grep -qF "REPAIRED:" <<<"$OUT" && ok "logged the REPAIRED: line (cache path)" || bad "did not log the cache-path repair"
163+
[ "$(alert_count)" -eq 0 ] && ok "no page on the cache-based origin repair" || bad "paged despite recovering origin from the cache"
164+
129165
# ============================================================================
130166
hr; echo "SELF-HEAL (a) — diverged+superseded, no writer: auto-healed, no page"; hr
131167
# The recurring real shape: a stale local-ahead commit, a dirty tracked file, and

0 commit comments

Comments
 (0)