fix(bin): let a spawn proceed on a project with no origin remote - #2874
fix(bin): let a spawn proceed on a project with no origin remote#2874matixacik-cell wants to merge 2 commits into
Conversation
fm-spawn's pooled-worktree base refresh unconditionally fetched origin and refused the spawn when that failed, so every fresh task worktree on a project that has no origin remote at all was refused with "could not fetch origin". Freshening asks whether a worktree's base is stale against its upstream. A repository with no origin configured has no upstream, so that question is meaningless rather than failed. Read git's local remote list first, which makes no network call, and skip the refresh with a loud notice when origin is not configured. A configured origin that cannot be reached, an unresolved default branch, a dirty worktree, and a stale base all still refuse exactly as before, so a network outage is never reclassified as "no remote". The check lives inside freshen_spawn_worktree_base so the freshening contract, including when it does not apply, stays with its one owner.
Confidence Score: 4/5The PR should not merge until the no-origin path prevents a pooled worktree containing a previous scout's committed work from becoming the next task's base. Scout teardown can return a detached worktree at an unlanded commit, while the new no-origin path checks only uncommitted status and then launches without restoring a known clean base. Files Needing Attention: bin/fm-spawn.sh Reviews (1): Last reviewed commit: "no-mistakes(review): refuse dirty pooled..." | Re-trigger Greptile |
| status=$(git -C "$1" status --porcelain) || return 2 | ||
| [ -z "$status" ] |
There was a problem hiding this comment.
Committed scout state passes clean check
When a scout commits changes before its scratch worktree is returned to a no-origin pool, git status --porcelain reports the detached worktree as clean and this path launches without resetting HEAD, causing the next task to branch on top of the previous scout's unlanded commit.
The defect
bin/fm-spawn.sh'sfreshen_spawn_worktree_base()unconditionally rangit -C "$worktree" fetch --quiet originand returned a hard error when that failed.A project with no
originremote configured, such as a purely local checkout that has never been pushed anywhere, therefore could not be spawned into a fresh pooled worktree at all. The fetch cannot succeed because there is no remote to fetch from, so every such spawn was refused with:Exact reproduction
Against the current default branch (
f170ced), with a repository that has nooriginremote:Work already running in previously initialised worktrees was unaffected, which is why this only surfaced once a fresh pool slot was needed.
The fix
Freshening asks one question: is this worktree's base stale against its upstream? A repository with no origin configured has no upstream, so that question is meaningless rather than failed.
freshen_spawn_worktree_base()now reads git's own local remote list first, which makes no network call, and skips the refresh with a loud notice whenoriginis not configured:The distinction is drawn on whether a remote is configured, never on whether a command failed. Every existing protection is unchanged for a repository that does have an origin: an origin that cannot be reached, a default branch that cannot be resolved, a dirty worktree, a failed reset, and a post-reset SHA mismatch all still refuse exactly as before. A network outage on an ordinary project is never reclassified as "no remote". A failure to read the remote list itself is also treated as a refusal, so the unsafe direction stays closed.
Skipping the refresh does not skip the unlanded-work protection. A pooled worktree carrying uncommitted work is still refused on the no-origin path, because a new task must never silently start on top of another task's unlanded work, and that risk is identical with or without an upstream. That path gets its own accurate message, since it is refusing to launch onto unlanded work rather than refusing to discard while refreshing:
The origin path's existing wording is untouched.
Placement
The check lives inside
freshen_spawn_worktree_base()rather than at its call site, so the freshening contract, including when it does not apply, stays with its one owner and no caller has to know what makes a base refresh meaningful.Tests
Colocated in the existing
tests/fm-spawn-pool-base-freshen.test.sh, covering both directions plus the retained protection:The no-origin test was proven to fail on the unfixed script (
expected exit 0, got 1) and pass with the fix.Scope
validate_spawn_worktree()'s isolation assertion is untouched, and nothing underprojects/is touched.bin/fm-spawn.sh's header and one paragraph indocs/architecture.mdare updated because the fix makes their previous wording inaccurate.