# Worktree freshness preflight

Version: 1.0
Updated: 2026-10-02
Method: https://suhailmohebi.com/notes/stale-worktree-that-passed/

Purpose: prove which tree a check ran against before its green result counts as evidence.

Output: a preflight record for each check: tree, revision, freshness against the base, other worktrees and a served-bytes match.

## Use when

Several agents or people work in parallel worktrees, and a passing build, test suite or browser check is about to be accepted as proof that a change is safe to release.

## Quick start

1. Run the preflight (section 1) with `--fetch` in the tree you are about to test. Clear every STOP line.
2. Prove the server your checks hit serves this tree’s bytes, and that untouched files and routes match production (section 2).
3. Record both next to the check’s result (section 3) before anyone reads it as a pass.

Test ownership before correctness: a green suite on the wrong tree proves only that one story is coherent.

## 1. Run the preflight in the tree under test

Ask one question of every green result: which tree did this check actually run against?

```bash
#!/usr/bin/env bash
# preflight.sh [--fetch] [base]   Read-only unless --fetch. Base defaults to origin/main.
set -u
fetch=no; [ "${1:-}" = "--fetch" ] && { fetch=yes; shift; }
base="${1:-origin/main}"; fail=0
top=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "STOP: not inside a git worktree"; exit 2; }
cd "$top" || exit 2
if [ "$fetch" = yes ]; then  # writes remote-tracking refs and FETCH_HEAD; never your branch or files
  git fetch --quiet "${base%%/*}" || { echo "STOP: fetch from ${base%%/*} failed"; exit 2; }
fi
branch=$(git symbolic-ref --quiet --short HEAD) || branch="(detached HEAD)"
echo "top level : $top"
echo "branch    : $branch"
echo "HEAD      : $(git rev-parse --short HEAD)"
if [ -n "$(git status --porcelain)" ]; then
  echo "tree      : DIRTY"; git status --short | head -n 10
  echo "STOP: uncommitted or untracked files in this tree"; fail=1
else
  echo "tree      : clean"
fi
if git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then
  read -r behind ahead <<< "$(git rev-list --left-right --count "$base...HEAD")"
  echo "vs $base : behind $behind, ahead $ahead"
  echo "merge-base: $(git log -1 --format='%h %cI' "$(git merge-base "$base" HEAD)")"
  [ "$behind" -gt 0 ] && { echo "STOP: $behind commit(s) on $base are missing from this tree"; fail=1; }
else
  echo "STOP: base $base not found; fetch it or name the right base"; fail=1
fi
newest=0; common=$(git rev-parse --git-common-dir)  # FETCH_HEAD is per worktree; remote refs are shared
for f in "$common"/FETCH_HEAD "$common"/worktrees/*/FETCH_HEAD; do
  if [ -f "$f" ]; then t=$(date -r "$f" +%s); [ "$t" -gt "$newest" ] && newest=$t; fi
done
age=unknown; [ "$newest" -gt 0 ] && age="$(( ($(date +%s) - newest) / 60 )) min ago"
echo "last fetch: $age"
[ "$newest" -gt 0 ] || { echo "STOP: no fetch recorded; re-run with --fetch"; fail=1; }
echo "worktrees :"
wt=$(git worktree list --porcelain | awk -v me="$top" -v br="$branch" '
  function show() { if (p != "") printf "  %s %s  [%s]%s\n", (p == me ? "*" : "-"), p, b,
                      (b == br && p != me ? "  WARN: same branch" : ""); p = "" }
  /^worktree / { show(); p = substr($0, 10); b = "detached" }
  /^branch /   { b = substr($0, 8); sub(/^refs\/heads\//, "", b) }
  /^bare$/     { b = "bare" }
  END          { show() }'); echo "$wt"
case "$wt" in *"WARN: same branch"*) echo "STOP: $branch is checked out in another worktree"; fail=1 ;; esac
if [ "$fail" -eq 0 ]; then echo "READY: matches $base as fetched ($age)"; else echo "NOT READY: fix the STOP lines first"; fi
exit "$fail"
```

- `git rev-list --left-right --count origin/main...HEAD` prints two numbers: left is commits on `origin/main` missing here (behind), right is commits only here (ahead, your own work).
- The comparison is only as fresh as the last fetch. `git fetch` updates remote-tracking refs and `FETCH_HEAD`, never your branch or files, so fetching is opt-in with `--fetch`.
- `FETCH_HEAD` is kept per worktree while remote-tracking refs are shared, so the script reads the newest one.
- Untracked files count as dirty; ignored build output does not, which is what section 2 is for.
- Exit codes: 0 ready, 1 not ready, 2 could not run.

## 2. Prove the tested thing is served from this tree

A clean, current tree proves nothing if the server or preview your checks hit was started from another worktree. Pick a file served byte-for-byte (a static asset, not a page the dev server rewrites):

```bash
# Read-only: is the server your checks hit serving this tree's build output?
set -o pipefail
FILE="dist/app.css"                   # a file served byte-for-byte, built inside THIS worktree
URL="http://localhost:8080/app.css"   # the same file as your browser or test runner receives it
sha() { if command -v shasum >/dev/null 2>&1; then shasum -a 256; else sha256sum; fi | cut -d' ' -f1; }
tree_sum=$(sha < "$FILE") || exit 2
served_sum=$(curl -fsS "$URL" | sha) || { echo "STOP: cannot fetch $URL"; exit 2; }
if [ "$tree_sum" = "$served_sum" ]; then
  echo "MATCH: $URL serves this tree's $FILE"
else
  echo "STOP: $URL is not serving this tree's $FILE"; exit 1
fi
```

Run it again with `URL` pointing at production and `FILE` set to a file your change does not touch. A STOP there means production and this tree disagree about work you did not do: another lane shipped it, `main` holds it undeployed, or the build is not reproducible. Find out which before releasing. Treat generated files and shared cache versions as release state: compare them the same way, and compare routes:

```bash
# Read-only: live routes this tree's build would drop (<) or add (>).
live=$(curl -fsS https://example.com/sitemap.xml) || { echo "STOP: cannot fetch the live sitemap"; exit 2; }
diff <(printf '%s\n' "$live" | grep -o '<loc>[^<]*' | sort -u) <(grep -o '<loc>[^<]*' dist/sitemap.xml | sort -u)
```

Every `<` line is a live page that would disappear if this tree shipped now.

## 3. Record the run before reading the result

| Check | Tree, branch @ HEAD | Behind / ahead (last fetch) | Other worktrees on this branch or surfaces | Served bytes and routes | Verdict |
|---|---|---|---|---|---|
|  |  | 0 / n (min ago) | none / list them | MATCH / STOP, no `<` lines | Accept / Reject |

Accept a green result only when its row shows READY, a recent fetch, no other worktree on the same branch or release surfaces, MATCH twice and no `<` route. Otherwise integrate in one lane and re-run there.

## Worked example

An agent reports that `feature/x` passes its unit and browser tests. The preflight in its worktree says otherwise:

```text
$ bash preflight.sh --fetch
top level : /work/site-feature
branch    : feature/x
HEAD      : abc1234
tree      : clean
vs origin/main : behind 3, ahead 2
merge-base: 9f8e7d6 2026-09-28T14:02:11+00:00
STOP: 3 commit(s) on origin/main are missing from this tree
last fetch: 0 min ago
worktrees :
  - /work/site  [main]
  * /work/site-feature  [feature/x]
  - /work/site-perf  [perf/cache]
NOT READY: fix the STOP lines first
```

The production check on `dist/assets/site.css`, which the change does not touch, also prints STOP: another lane shipped a newer stylesheet.

**Verdict:** reject the green tests as release evidence. Merge `origin/main` into `feature/x`, rebuild and re-run everything in that one tree. Accept only when the preflight shows READY (behind 0, ahead 3), both served-bytes checks MATCH and no live route is missing.

## Stop if

- The preflight prints STOP: integrate or clean this tree, then re-run every check. Never weaken a test so a stale tree passes.
- Another worktree has the same branch checked out (`WARN: same branch`): one of them is changing files under the other.
- The last fetch is unknown or older than the latest push you know of: re-run with `--fetch`.
- Served bytes do not match this tree: discard every result from that run.
- An untouched file differs from production, or the live deploy cannot be matched to a revision: find the owning lane before releasing.
- Several worktrees touch the same release surfaces: create one integration lane and test only there.

## Sources

- Git: git-rev-list documentation (`--left-right`, `--count`) — https://git-scm.com/docs/git-rev-list (checked 2026-10-02)
- Git: git-worktree documentation (porcelain format) — https://git-scm.com/docs/git-worktree (checked 2026-10-02)
