Skip to content

Refuse to start against a base branch that has fallen behind - #183

Merged
thedancingdeveloper merged 1 commit into
mainfrom
fix/preflight-refuses-a-stale-base
Aug 5, 2026
Merged

Refuse to start against a base branch that has fallen behind#183
thedancingdeveloper merged 1 commit into
mainfrom
fix/preflight-refuses-a-stale-base

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Contributor

Closes #180.

A stale base is the one wrong setting every later stage reports as a success — agent works, checks pass, reviewer approves, commit lands, onto a lineage nobody develops on. Measured on rdpapp: 121 commits behind the authoritative remote, six items delivered onto it, all discarded.

Verified against the real repository:

harness/base is 121 commit(s) behind origin/master and 27 ahead. Work based
here lands on a lineage that has moved on, and every stage after this one —
the agent, the checks, the reviewer, the commit — will report success while
it happens.

Two flaws caught by tests before commit:

  • The first version passed the exact case it was written for. It bailed with "no single obvious line of work to compare against" when a base tracks nothing and there are multiple remotes — the rdpapp shape. Now measures against every remote's default head.
  • Then an unrelated two-commit remote vouched for a base thirty behind the real one. Now only remotes sharing history count as candidate lineages.

Runs in both modes: a pristine worktree of the wrong lineage is still the wrong lineage.

Five tests. All four gates green locally.

🤖 Generated with Claude Code

A stale base is the one wrong setting every later stage reports as a success.
The agent works, the checks pass, the reviewer approves, the commit lands — onto
a lineage nobody develops on any more. There is no failure to notice, so
nothing noticed, and the cost is not one item but every item in the run.

Measured on rdpapp: work was based on a branch cut from a local working tree
that turned out to be 121 commits behind the authoritative remote and 27 ahead,
carrying an alternate implementation never promoted into the released lineage.
Six items were delivered onto it before a human who knew the lineage said so.
All six are discarded.

`preflight` fetches once and compares. Behind by a little is reported and
allowed, because every branch is a few commits behind and blocking on that
would make the check noise nobody reads. Behind by more than
`STALE_BASE_LIMIT` is refused, with `--allow-stale-base` to override, mirroring
`--allow-dirty`.

Two things this gets right only because a test caught them getting it wrong:

**A branch that tracks nothing is still measured.** The first version returned
"no single obvious line of work to compare against" when a base had no upstream
and the repository had several remotes — which is the rdpapp shape exactly, so
it passed the case it was written for. It now compares against every remote's
default head, because "which remote is authoritative" is not a question a
preflight check can answer and not one it should guess at.

**An unrelated remote cannot vouch for a stale base.** Measuring against every
remote then let a two-commit unrelated repository, sitting in the same checkout
as a second remote, mark a base thirty behind the real one as current. Only
remotes sharing history with the base count as candidate lineages.

Unreachable remotes, absent upstreams and repositories with no remote at all
are reported and pass. None of them are evidence that a base is stale, and
reporting them as failures trains people to pass the override by default.

Runs in **both** modes, unlike the dirty-tree check: session mode gives each
item a pristine worktree, and a pristine worktree of the wrong lineage is still
the wrong lineage.

Closes #180.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedancingdeveloper
thedancingdeveloper merged commit 5437eb1 into main Aug 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

preflight does not notice that the base branch is stale, and a whole run can be spent on a dead lineage

1 participant