Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
45429af
fix: isolate task temp roots across runs
ruby-dlee Jul 26, 2026
718d5ee
no-mistakes(review): Make task temp roots generation-unique
ruby-dlee Jul 26, 2026
405fd7d
no-mistakes(review): Clean prior task temp after respawn
ruby-dlee Jul 26, 2026
8c1d4c2
no-mistakes(review): Safely remove task temp generations
ruby-dlee Jul 26, 2026
78c1785
no-mistakes(review): Pin task temp ancestors during removal
ruby-dlee Jul 26, 2026
8c6d026
no-mistakes(review): Fail closed on partial temp removal
ruby-dlee Jul 26, 2026
6385f9d
no-mistakes(test): Fix teardown descriptors and flaky process observa…
ruby-dlee Jul 26, 2026
c087f6d
fix: reject hidden secondmate routing drift
ruby-dlee Jul 26, 2026
5b0b699
fix: make routing failure fixtures deterministic
ruby-dlee Jul 27, 2026
aa3339b
no-mistakes(review): Harden rollback task-temp cleanup and test hooks
ruby-dlee Jul 27, 2026
a941bcf
no-mistakes(test): Fix default-ref routing and teardown test fixtures
ruby-dlee Jul 27, 2026
a4ce964
no-mistakes(test): Stabilize account routing sync-all timeout fixture
ruby-dlee Jul 27, 2026
27751f7
no-mistakes(test): Stabilize Herdr routing and backend test fixtures,…
ruby-dlee Jul 27, 2026
f891634
no-mistakes(test): Stabilize routing and lifecycle test fixtures
ruby-dlee Jul 27, 2026
5328cc4
no-mistakes(test): Stabilize AFK and secondmate lifecycle fixtures
ruby-dlee Jul 27, 2026
f1fb21b
no-mistakes(document): Document generation-safe routing and Zellij li…
ruby-dlee Jul 27, 2026
333c17d
test: reconcile routing fixtures after rebase
ruby-dlee Jul 28, 2026
08403a8
no-mistakes(test): Stabilize Herdr secondmate primary ref fixture
ruby-dlee Jul 28, 2026
c5164cc
no-mistakes(document): Update routing and liveness documentation
ruby-dlee Jul 28, 2026
99cf75d
test(orca): preserve expected failure captures under errexit
ruby-dlee Jul 28, 2026
521b84f
no-mistakes(review): Harden rollback task temp cleanup
ruby-dlee Jul 28, 2026
dd52b12
no-mistakes(review): Exercise rollback temp cleanup failures
ruby-dlee Jul 28, 2026
c8e4330
no-mistakes(document): Correct stale routing and teardown documentation
ruby-dlee Jul 28, 2026
28ffc42
no-mistakes: apply CI fixes
ruby-dlee Jul 28, 2026
6c68460
fix: stabilize Orca secondmate teardown checks
ruby-dlee Jul 28, 2026
35f87e7
fix: fail closed on checkout reinspection
ruby-dlee Jul 28, 2026
01284de
ci: allow behavior suite to complete
ruby-dlee Jul 28, 2026
6609f2c
test: stabilize secondmate lifecycle git fixture
ruby-dlee Jul 28, 2026
1063b2c
test: reconcile routing fixtures with updated base
ruby-dlee Jul 29, 2026
0e5b7ae
test: preserve Orca failure capture after rebase
ruby-dlee Jul 29, 2026
4130ede
test: make routing race fixtures deterministic
ruby-dlee Jul 29, 2026
82d3465
test: make checkout reinspection fixture observable
ruby-dlee Jul 29, 2026
e0afca5
test: use observable stale lock owners
ruby-dlee Jul 29, 2026
7e18d3e
test: isolate teardown task temp fixture
ruby-dlee Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/bootstrap-diagnostics/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The inline rules in `AGENTS.md` section 3 still bind: detect, then consent, then
- `FLEET_SYNC: <repo>: STUCK: on <state>, N commits behind <base> - needs attention` - the clone is dirty, on a non-default branch, detached with unique commits, or diverged, so the sync left it untouched (never forcing or discarding); it will keep falling behind until you look.
A loud STUCK, especially a growing N across bootstraps, means that clone needs hands-on attention; dispatch a crewmate or resolve it before it strands work.
- `SECONDMATE_SYNC: secondmate <id>: skipped: <reason>` - the local-HEAD secondmate sync left a live secondmate home on its existing checkout because the home was dirty, diverged, unsafe, on the wrong branch, missing the primary target commit, or otherwise not fast-forwardable, or because inheritable-config propagation failed; bootstrap continued, but inspect the reason because the secondmate's tracked instructions or inherited settings may be stale after a primary update.
- `SECONDMATE_LIVENESS: secondmate <id>: <outcome>` - the session-start liveness sweep checked a live secondmate's recorded endpoint for a real agent process.
- `SECONDMATE_LIVENESS: secondmate <id>: <outcome>` - the session-start liveness sweep checked a live secondmate's recorded endpoint with that backend's classifier; `docs/configuration.md` "Runtime backend" owns the possible verdicts.
Treat `already-live`, `respawned`, and `rollback reconciled and respawned` as handled; investigate `skipped` or `respawn failed`, and load `secondmate-provisioning` for any `respawn deferred` outcome because its "Recovery" section owns the required routing decision.
- `TASKS_AXI: available` - a default-backend capability fact, not a problem; record it silently and use `AGENTS.md` section 10 for backlog mutations.
It prints only when `config/backlog-backend` is absent or set to `tasks-axi` and the shared compatibility probe passes (`docs/configuration.md` "Backlog backend").
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ An explicit `--account-pool` or `--account-profile` overrides it for that spawn,
This pool is not inherited into the secondmate home because it governs the primary's secondmate launch, while `config/account-routing-mode` is inherited so the secondmate applies the same off/observe/enforce policy to its own crewmates.

This section is the single owner of the secondmate sync and inheritable-config propagation contract; `AGENTS.md` sections 3 and 4 point here.
Before launch, `fm-spawn.sh --secondmate` locally fast-forwards the home to the primary firstmate checkout's current default-branch commit when it is safe; dirty, diverged, or in-flight homes launch unchanged with a warning.
Before launch, `fm-spawn.sh --secondmate` locally fast-forwards the home to the primary firstmate checkout's current default-branch commit when it is safe; other dirty, diverged, or in-flight homes launch unchanged with a warning, subject to the account-routing compatibility refusal owned by `docs/configuration.md` "Agent Fleet account routing."
The locked session-start bootstrap sweep runs the same guarded fast-forward for every live secondmate home, discovered from `state/<id>.meta` records with `kind=secondmate` (`data/secondmates.md` only backfills `home=` for older records).
That no-fetch path is a purely local fast-forward of tracked files, never an origin fetch, and it never touches the gitignored operational dirs, so a secondmate's backlog, projects, and in-flight work are never disturbed; a linked worktree advances immediately, while a standalone clone that lacks the target receives firstmate updates through `/updatefirstmate`'s origin refresh.
The same launch and the same locked bootstrap sweep also propagate the primary's declared inheritable local config, currently `config/crew-dispatch.json`, `config/crew-harness`, `config/backlog-backend`, and `config/account-routing-mode`, into the secondmate home's `config/`.
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ jobs:
tests:
name: Behavior tests
runs-on: ubuntu-latest
# The pre-cutover suite reached 25m35s, and the exhaustive Bridge crash
# matrix adds bounded work. Keep enough margin without masking a real hang.
timeout-minutes: 45
# The expanded suite was still making forward progress when the prior 45m
# budget expired. Allow measured headroom while retaining a finite hang cap.
timeout-minutes: 90
steps:
- uses: actions/checkout@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ It composes today's `fm-lock.sh`, `fm-bootstrap.sh`, and `fm-wake-drain.sh` - ca
2. **Bootstrap** - detect-only diagnostics (tool/version problems, GitHub auth, the worktree-tangle check, harness override, dispatch-profile validation, backlog-backend status) always run and always print.
When the lock could not be acquired, the worktree-tangle check uses read-only advisory wording without a checkout repair command.
The five MUTATING sweeps - report-retention ownership, fleet sync, the local secondmate fast-forward sweep, the secondmate liveness sweep, and X-mode artifact writes - run only when this session actually holds the lock from step 1.
The secondmate liveness sweep probes each live secondmate's endpoint for a real agent process, reports handled or deferred outcomes as `SECONDMATE_LIVENESS:` lines, and leaves any required routing decision to `secondmate-provisioning` (`bin/fm-bootstrap.sh`; `bin/fm-backend.sh`'s `fm_backend_agent_alive`).
The secondmate liveness sweep probes each live secondmate's endpoint with the backend-specific classifier owned by `docs/configuration.md` "Runtime backend", reports handled or deferred outcomes as `SECONDMATE_LIVENESS:` lines, and leaves any required routing decision to `secondmate-provisioning` (`bin/fm-bootstrap.sh`; `bin/fm-backend.sh`'s `fm_backend_agent_alive`).
3. **Wake queue** - when locked, drains the durable wake queue and prints the records prominently as this turn's first work queue, exactly as `bin/fm-wake-drain.sh` did before; a lapsed watcher chain still surfaces here via the same guard banner.
When the lock could not be acquired, the queue is left untouched because another session owns it, and the guard's tangle/watcher-liveness alarms still print in read-only advisory mode without drain, supervision repair, or checkout repair commands.
4. **Context digest** - the full contents of `data/projects.md`, `data/secondmates.md`, `data/captain.md`, and `data/learnings.md`, each clearly delimited.
Expand Down
1 change: 1 addition & 0 deletions bin/backends/herdr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2672,6 +2672,7 @@ fm_backend_herdr_server_reachable_for_readsteer() { # <session>
running=$(fm_backend_herdr_cli "$session" status --json 2>/dev/null \
| fm_backend_herdr_control_jq -r '.server.running // false' 2>/dev/null)
[ "$running" = true ] || return 1
fm_backend_herdr_server_certificate_required || return 0
fm_backend_herdr_server_adapter_owned "$session"
}

Expand Down
23 changes: 23 additions & 0 deletions bin/backends/zellij.sh
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,29 @@ fm_backend_zellij_target_ready() { # <target> [expected-label]
fm_backend_zellij_pane_exists "$FM_BACKEND_ZELLIJ_SESSION" "$FM_BACKEND_ZELLIJ_PANE"
}

fm_backend_zellij_agent_alive() { # <target>
local panes
fm_backend_zellij_parse_target "$1" || { printf 'unknown'; return 0; }
fm_backend_zellij_session_exists "$FM_BACKEND_ZELLIJ_SESSION" || { printf 'dead'; return 0; }
panes=$(fm_backend_zellij_cli "$FM_BACKEND_ZELLIJ_SESSION" action list-panes --json 2>/dev/null) \
|| { printf 'unknown'; return 0; }
printf '%s\n' "$panes" | jq -e '
type == "array"
and all(.[];
type == "object"
and has("id")
and .id != null
and (.is_plugin | type) == "boolean"
and (.is_plugin or (has("tab_id") and .tab_id != null)))
' >/dev/null 2>&1 || { printf 'unknown'; return 0; }
if printf '%s\n' "$panes" | jq -e --arg pane "$FM_BACKEND_ZELLIJ_PANE" \
'any(.[]?; .is_plugin == false and (.id | tostring) == $pane)' >/dev/null 2>&1; then
printf 'unknown'
else
printf 'dead'
fi
}

# fm_backend_zellij_current_path: the live pane's cwd, or empty on any error.
# Mirrors tmux's pane_current_path poll used for worktree-path discovery after
# `treehouse get`.
Expand Down
111 changes: 105 additions & 6 deletions bin/fm-account-routing-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,99 @@ fm_account_real_directory() {
[ -d "$1" ] && [ ! -L "$1" ]
}

fm_account_task_tmp_path() { # <task-id> <generation-id>
local task=$1 generation=$2 token state
fm_account_valid_id "$task" || return 1
case "$generation" in
spawn:*|account:*)
token=${generation##*:}
;;
*)
return 1
;;
esac
fm_account_valid_id "$token" || return 1
if [ -n "${STATE:-}" ]; then
state=$STATE
elif [ -n "${FM_STATE_OVERRIDE:-}" ]; then
state=$FM_STATE_OVERRIDE
elif [ -n "${FM_HOME:-}" ]; then
state=$FM_HOME/state
else
return 1
fi
fm_account_real_directory "$state" || return 1
state=$(cd "$state" 2>/dev/null && pwd -P) || return 1
[ "$state" != / ] || return 1
printf '%s/.task-tmp/fm-%s-%s\n' "${state%/}" "$task" "$token"
}

fm_account_previous_task_tmp_path() { # <task-id>
local task=$1 state
fm_account_valid_id "$task" || return 1
if [ -n "${STATE:-}" ]; then
state=$STATE
elif [ -n "${FM_STATE_OVERRIDE:-}" ]; then
state=$FM_STATE_OVERRIDE
elif [ -n "${FM_HOME:-}" ]; then
state=$FM_HOME/state
else
return 1
fi
fm_account_real_directory "$state" || return 1
state=$(cd "$state" 2>/dev/null && pwd -P) || return 1
[ "$state" != / ] || return 1
printf '%s/.task-tmp/fm-%s\n' "${state%/}" "$task"
}

fm_account_legacy_task_tmp_path() { # <task-id>
fm_account_valid_id "$1" || return 1
printf '/tmp/fm-%s\n' "$1"
}

fm_account_task_tmp_is_current() { # <task-id> <path> <generation-id>
local expected
expected=$(fm_account_task_tmp_path "$1" "$3") || return 1
[ "$2" = "$expected" ]
}

fm_account_task_tmp_is_previous() { # <task-id> <path>
local previous
previous=$(fm_account_previous_task_tmp_path "$1") || return 1
[ "$2" = "$previous" ]
}

fm_account_task_tmp_is_legacy() { # <task-id> <path>
local legacy
legacy=$(fm_account_legacy_task_tmp_path "$1") || return 1
[ "$2" = "$legacy" ]
}

fm_account_task_tmp_is_expected() { # <task-id> <path> <generation-id>
# Legacy metadata remains recognizable, but callers must use
# fm_account_task_tmp_is_current before any filesystem mutation.
fm_account_task_tmp_is_current "$1" "$2" "$3" \
|| fm_account_task_tmp_is_previous "$1" "$2" \
|| fm_account_task_tmp_is_legacy "$1" "$2"
}

fm_account_safe_remove_task_tmp() { # <task-id> <path> <generation-id> [subdirectory]
local task=$1 target=$2 generation=$3 child=${4:-} legacy
[ -n "$target" ] || return 0
fm_account_task_tmp_is_expected "$task" "$target" "$generation" || return 1
legacy=$(fm_account_legacy_task_tmp_path "$task") || return 1
[ "$target" != "$legacy" ] || return 0
fm_account_task_tmp_is_current "$task" "$target" "$generation" \
|| fm_account_task_tmp_is_previous "$task" "$target" \
|| return 1
case "$child" in
'') ;;
gotmp) target="$target/$child" ;;
*) return 1 ;;
esac
python3 "$FM_ACCOUNT_ROUTING_LIB_DIR/fm-safe-task-tmp.py" "$target" || return 1
}

fm_account_safe_file_destination() {
[ ! -L "$1" ] && { [ ! -e "$1" ] || [ -f "$1" ]; }
}
Expand Down Expand Up @@ -1195,7 +1288,7 @@ fm_account_meta_value() { # <meta> <key>
}

fm_account_restore_artifacts() {
local state=$1 task=$2 backup_name=$3 tasktmp=${4:-} retain=${5:-0} backup name source
local state=$1 task=$2 backup_name=$3 tasktmp=${4:-} retain=${5:-0} generation=${6:-} backup name source
local PATH=$FM_ACCOUNT_SYSTEM_PATH
[ -n "$backup_name" ] || return 0
case "$backup_name" in
Expand All @@ -1213,11 +1306,17 @@ fm_account_restore_artifacts() {
fi
done
if [ -n "$tasktmp" ]; then
[ "$tasktmp" = "/tmp/fm-$task" ] || return 1
if [ -e "$backup/tasktmp-existed" ]; then
[ -e "$backup/gotmp-existed" ] || fm_account_system_exec "$FM_ACCOUNT_SYSTEM_RM_BIN" -rf "$tasktmp/gotmp" || return 1
if fm_account_task_tmp_is_current "$task" "$tasktmp" "$generation" \
|| fm_account_task_tmp_is_previous "$task" "$tasktmp"; then
if [ -e "$backup/tasktmp-existed" ]; then
[ -e "$backup/gotmp-existed" ] || fm_account_safe_remove_task_tmp "$task" "$tasktmp" "$generation" gotmp || return 1
else
fm_account_safe_remove_task_tmp "$task" "$tasktmp" "$generation" || return 1
fi
else
fm_account_system_exec "$FM_ACCOUNT_SYSTEM_RM_BIN" -rf "$tasktmp" || return 1
# A legacy tasktmp is metadata-only compatibility. Never inspect or
# mutate the shared /tmp path while restoring task-owned artifacts.
fm_account_task_tmp_is_legacy "$task" "$tasktmp" || return 1
fi
fi
[ "$retain" = 1 ] || fm_account_system_exec "$FM_ACCOUNT_SYSTEM_RM_BIN" -rf "$backup"
Expand Down Expand Up @@ -1289,7 +1388,7 @@ fm_account_cleanup_rollback() { # <meta> <data-dir> <task>
if [ "$preserve" != 1 ]; then
fm_account_session_remove "$account_task" || return 1
fi
fm_account_restore_artifacts "$(fm_account_system_exec "$FM_ACCOUNT_SYSTEM_DIRNAME_BIN" "$meta")" "$task" "$artifacts_name" "$tasktmp" 1 || return 1
fm_account_restore_artifacts "$(fm_account_system_exec "$FM_ACCOUNT_SYSTEM_DIRNAME_BIN" "$meta")" "$task" "$artifacts_name" "$tasktmp" 1 "$(fm_account_meta_value "$meta" generation_id)" || return 1
lock=$(fm_account_meta_lock_acquire "$(fm_account_system_exec "$FM_ACCOUNT_SYSTEM_DIRNAME_BIN" "$meta")" "$task") || return 1
if [ ! -f "$meta" ] \
|| [ "$(fm_account_meta_value "$meta" account_task)" != "$account_task" ] \
Expand Down
12 changes: 7 additions & 5 deletions bin/fm-backend.sh
Original file line number Diff line number Diff line change
Expand Up @@ -927,8 +927,7 @@ fm_backend_target_state() { # <backend> <target> [expected-label] [recorded-sco
*) printf 'unknown'; return 0 ;;
esac
if [ "$pane_record" = null ]; then
if [ -n "$expected_tab_id" ] && printf '%s\n' "$panes" | jq -e --arg tab "$expected_tab_id" \
'any(.[]?; .is_plugin == false and (.tab_id | tostring) == $tab)' >/dev/null 2>&1; then
if [ -n "$expected_tab_id" ]; then
printf 'present'
else
printf 'absent'
Expand Down Expand Up @@ -1035,9 +1034,11 @@ fm_backend_target_state() { # <backend> <target> [expected-label] [recorded-sco
# Scoped to today's --secondmate-spawn-capable backends with an empirically
# verified classifier: tmux (docs/tmux-backend.md "Agent liveness probe") and
# herdr (docs/herdr-backend.md "Agent liveness probe reuses the husk
# classifier"). zellij, orca, and cmux report unknown until independently
# verified - future work, not a functional gap for the two backends
# --secondmate spawns actually support today plus tmux's reference path.
# classifier"). Zellij can report dead only when the recorded session or pane
# is structurally absent; a present pane remains unknown. Orca and cmux report
# unknown until independently verified - future work, not a functional gap for
# the two backends --secondmate spawns actually support today plus tmux's
# reference path.
# Callers must treat unknown exactly like an unreadable target: NEVER license
# an action from it alone - the secondmate-liveness sweep gates a respawn on
# `dead` only, precisely so a momentary read glitch can never duplicate a
Expand All @@ -1048,6 +1049,7 @@ fm_backend_agent_alive() { # <backend> <target> [expected-label] [recorded-scop
case "$backend" in
tmux) fm_backend_tmux_agent_alive "$target" "$expected_label" "${4:-}" ;;
herdr) fm_backend_herdr_agent_alive "$target" "$expected_label" ;;
zellij) fm_backend_zellij_agent_alive "$target" ;;
*) printf 'unknown' ;;
esac
}
Expand Down
19 changes: 16 additions & 3 deletions bin/fm-checkout-lock-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fm_checkout_git_common_dir() {
}

fm_checkout_validate_git_metadata() {
local checkout=$1 root metadata absolute_git common listed line listed_root found=0
local checkout=$1 root metadata absolute_git common top listed line listed_root found=0
root=$(fm_checkout_trusted_dir "$checkout") || return 1
metadata="$root/.git"
[ -e "$metadata" ] && [ ! -L "$metadata" ] || return 1
Expand All @@ -118,11 +118,16 @@ fm_checkout_validate_git_metadata() {
*) common="$root/$common" ;;
esac
common=$(fm_checkout_trusted_dir "$common") || return 1
top=$(git -C "$root" rev-parse --show-toplevel 2>/dev/null) || return 1
top=$(fm_checkout_trusted_dir "$top") || return 1
[ "$top" = "$root" ] || return 1
if [ -d "$metadata" ]; then
[ "$(fm_checkout_trusted_dir "$metadata")" = "$absolute_git" ] || return 1
[ "$absolute_git" = "$common" ] || return 1
elif [ -f "$metadata" ]; then
case "$absolute_git" in "$common"/worktrees/*) ;; *) return 1 ;; esac
if [ "$absolute_git" != "$common" ]; then
case "$absolute_git" in "$common"/worktrees/*) ;; *) return 1 ;; esac
fi
else
return 1
fi
Expand All @@ -131,7 +136,15 @@ fm_checkout_validate_git_metadata() {
case "$line" in
"worktree "*)
listed_root=$(fm_checkout_trusted_dir "${line#worktree }" 2>/dev/null) || return 1
[ "$listed_root" != "$root" ] || found=$((found + 1))
if [ "$listed_root" = "$root" ]; then
found=$((found + 1))
elif [ -f "$metadata" ] && [ "$absolute_git" = "$common" ] \
&& [ "$listed_root" = "$common" ]; then
# An absorbed submodule's worktree listing identifies its common
# Git directory rather than the checked-out root. The exact
# show-toplevel proof above binds that Git directory back to root.
found=$((found + 1))
fi
;;
esac
done <<EOF
Expand Down
18 changes: 16 additions & 2 deletions bin/fm-checkout-refresh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1882,6 +1882,18 @@ record_reinspection_failure() {
}
}

reinspect_covered_checkout() {
local checkout=$1 count
if [ "${FM_CHECKOUT_REFRESH_TEST:-0}" = 1 ] \
&& [ "${FM_CHECKOUT_TEST_REINSPECTION_FAILURE_AT:-}" = "$checkout" ]; then
count=$(cat "${FM_CHECKOUT_TEST_REINSPECTION_COUNT:?}" 2>/dev/null || printf 0)
count=$((count + 1))
printf '%s\n' "$count" > "$FM_CHECKOUT_TEST_REINSPECTION_COUNT"
return 1
fi
exact_git_root "$checkout"
}

CHECKOUT_REFRESH_AUTHORITY=
resolve_checkout_refresh_authority() {
local checkout=$1 mode_line mode
Expand Down Expand Up @@ -2423,9 +2435,10 @@ run_once() {

while IFS= read -r checkout; do
[ -n "$checkout" ] || continue
if ! reinspected=$(exact_git_root "$checkout") || [ "$reinspected" != "$checkout" ]; then
if ! reinspected=$(reinspect_covered_checkout "$checkout") || [ "$reinspected" != "$checkout" ]; then
record_reinspection_failure "$checkout" || status=1
coverage_failed=1
status=1
continue
fi
key=$(checkout_key "$checkout") || {
Expand All @@ -2447,9 +2460,10 @@ run_once() {

while IFS= read -r checkout; do
[ -n "$checkout" ] || continue
if ! reinspected=$(exact_git_root "$checkout") || [ "$reinspected" != "$checkout" ]; then
if ! reinspected=$(reinspect_covered_checkout "$checkout") || [ "$reinspected" != "$checkout" ]; then
record_reinspection_failure "$checkout" || status=1
coverage_failed=1
status=1
continue
fi
key=$(checkout_key "$checkout") || {
Expand Down
4 changes: 3 additions & 1 deletion bin/fm-ff-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ default_branch() {
primary_head_commit() {
local root=$1 default
default=$(default_branch "$root") || return 1
git -C "$root" rev-parse --verify --quiet "refs/heads/$default^{commit}" 2>/dev/null || return 1
git -C "$root" rev-parse --verify --quiet "refs/heads/$default^{commit}" 2>/dev/null \
|| git -C "$root" rev-parse --verify --quiet "refs/remotes/origin/$default^{commit}" 2>/dev/null \
|| return 1
}

resolve_path() {
Expand Down
Loading