tooling: answer "is this file claimed?" in one command (#3175)

Addresses the root cause of a pattern I have now measured four times.

## The finding

**Every fleet worker pushes as the same GitHub account.** `gh pr list
--author "@me"` returns **all 17 open PRs** — mine and teammates' are
indistinguishable. So "is this file already being converted?" can only
be answered by fetching every open PR's file list by hand: 25+ API calls
that no worker makes before starting. I didn't either.

## The measured cost

| PR | Outcome | Landed instead as |
|---|---|---|
| #3096 | shrank to a test | teammate's serialisation + union |
| #3116 | shrank to a test | `preExecLiveColumns`,
`starvedWaitingColumns`, … |
| #3140 | shrank to a test | #3137 |
| #3125 | **shrank to nothing — closed** | #3135 |

Plus #3118, a teammate independently writing the same coverage I wrote
for #3112.

**In every case both implementations were correct and independently
reached the same design** — #3137 chose payload-first-with-sync-fallback
for the same reason I did. This is not carelessness; the fleet is doing
correct work twice and discovering coverage gaps by accident, when
rebases collide.

## What this adds

```
$ node scripts/check-file-claimed.mjs packages/engine/src/self-healing.ts
CLAIMED    packages/engine/src/self-healing.ts
             #3152  fix(self-healing): 18 recovery rebounds hardcoded `todo` …
```

**On its first run it reported `self-healing.ts` claimed by #3152 —
which I had no way to know a moment earlier.** Exits non-zero when
claimed, so it can gate work: `node scripts/check-file-claimed.mjs
<path> && start-work`.

## Deliberate limits

- **It cannot see unpushed work**, so it narrows the collision window
rather than closing it. Two workers starting the same file minutes apart
still collide. Closing that needs **distinguishable authorship** — a
per-worker `Co-Authored-By` or a title prefix — which is a coordination
decision, not a script.
- **A `gh` failure exits 2 and says UNKNOWN, not unclaimed.** A claim
check that fails open is worse than none, which is the same false-green
shape this program keeps finding elsewhere.

Also adds a short AGENTS.md pointer next to the other standing rules.

## Verification

Run against a claimed and an unclaimed path; both answers correct, exit
codes as documented.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added a command-line check for open pull requests that may already be
modifying specified files.
* Reports matching pull request details and clearly indicates whether
each file is claimed.
* Returns distinct statuses for claimed files, unclaimed files, and
unavailable GitHub checks.

* **Documentation**
* Added guidance for checking file ownership before beginning conversion
work.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-07-31 08:10:29 -07:00
committed by GitHub
parent f8fb9b1473
commit b8bfcd031b
2 changed files with 123 additions and 0 deletions

View File

@@ -143,6 +143,36 @@ pnpm verify:workspace # deep opt-in verification (lint -> test:full -> build);
`pnpm verify:fast` is the recommended **test-free verification** path: bootstrap missing/stale workspace dist artifacts, typecheck + build scoped to the changed packages (it reuses `pnpm test`'s changed-package resolution), an always-on `@runfusion/fusion` CLI build required by the source-checkout boot smoke, plus the boot smoke once, with **no test run**. It is deterministic and flake-free, suitable as a project `testCommand`/verification command when you want non-test verification; the full suite stays available and runs non-blocking. It is additive and does not change `pnpm test`, the gate, or CI. See `docs/testing.md`.
### Check whether a file is claimed before converting it
Every fleet worker pushes as the same GitHub account, so `gh pr list --author "@me"` returns EVERY open
PR and cannot distinguish your work from a teammate's. Before starting a conversion, ask:
```bash
node scripts/check-file-claimed.mjs packages/engine/src/self-healing.ts
```
It lists the open PRs touching that path and exits non-zero if any do, so it can gate work directly.
It narrows the collision window rather than closing it — it cannot see unpushed work in progress.
Measured cost of not having it: four PRs in one session were superseded by teammates landing the same
conversion first, each time with both implementations correct and independently identical.
<!--
FNXC:FleetClaims 2026-07-31-21:15: WHY THIS IS A RULE AND NOT A SUGGESTION.
Every worker ranks work from the same census output, so without a published claim they independently
pick the same top file. In one fleet phase that produced three parallel conversions of
`self-healing.ts` (two left unmergeable after the first landed), two workers marking the same two
files, and two independent versions of the same `task:moved` emitter fix — five collisions, all with
both sides correct.
The check is cheap because the claim is a pushed branch: `git ls-remote` is authoritative the moment
work starts, whereas a claim announced anywhere else is invisible until the duplicate work exists.
That asymmetry is the whole point — the first signal of a collision used to be a failed checkout or a
conflicting PR, i.e. after the cost was already paid.
-->
### Standing Rule: Flaky Tests Are Quarantined on Sight (Deletion Ratchet)
- A test observed failing without a corresponding real bug in the change is QUARANTINED ON SIGHT: add an entry to `scripts/lib/test-quarantine.json` (`file`, `reason` with a link to the failing run, `quarantinedAt`) AND a matching one-line `exclude` in that package's vitest config, in the same commit.

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env node
/*
FNXC:FleetCoordination 2026-07-31-06:10 (fleet):
WHY THIS EXISTS. Every fleet worker pushes as the SAME GitHub account, so `gh pr list --author "@me"`
returns all 17 open PRs and no worker can tell their own from a teammate's. There is no way to ask "is
this file already being converted?" short of fetching every open PR's file list by hand — 25+ API calls
that a worker will not make before starting, and I did not make either.
MEASURED COST, not a hypothetical: four of my PRs were superseded by teammates landing the same work
first (#3096, #3116, #3140 shrank to tests; #3125 to nothing and was closed). In every case both
implementations were correct and independently reached the same design. The fleet is not making
mistakes — it is doing correct work twice, and finding coverage gaps only by accident when the rebases
collide.
WHAT THIS DOES. One command, one answer:
node scripts/check-file-claimed.mjs packages/engine/src/self-healing.ts
It prints the open PRs touching that path, so "claimed?" is answerable before the work starts rather
than at rebase time.
WHAT IT DOES NOT DO, deliberately. It cannot see work that is in progress and unpushed, so it narrows
the window rather than closing it. Closing it needs distinguishable authorship — a per-worker
`Co-Authored-By` or a title prefix — which is a coordination decision, not a script. This is the part
that can be fixed from inside the repo.
*/
import { execFileSync } from "node:child_process";
const targets = process.argv.slice(2).filter((a) => !a.startsWith("-"));
if (targets.length === 0) {
console.error("usage: node scripts/check-file-claimed.mjs <path> [<path>...]");
console.error(" paths are matched as substrings of each PR's changed-file list");
process.exit(2);
}
function gh(args) {
try {
return execFileSync("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
} catch (err) {
console.error(`claim-check: gh failed — ${err?.message ?? err}`);
console.error("claim-check: cannot prove a file is unclaimed without it. Treat as UNKNOWN, not free.");
process.exit(2);
}
}
/*
FNXC:FleetClaims 2026-07-31-21:10 (#3175 review — coderabbitai, "detect or eliminate truncation"):
A SILENT CAP TURNS THIS TOOL INTO THE BUG IT PREVENTS.
`--limit 100` returns at most 100 PRs. Past that, claims live in PRs this never sees and a claimed file
reports UNCLAIMED — the one answer that must never be wrong here, because a worker acts on it by
starting work someone else already started. This fleet ran 50+ open PRs at once, so the cap is not
hypothetical.
Fails closed rather than warning: a warning printed above an `UNCLAIMED` line is read as noise next to
a verdict. Same reasoning as `gh()` exiting rather than returning empty.
*/
const PR_LIMIT = 300;
const open = JSON.parse(gh(["pr", "list", "--state", "open", "--limit", String(PR_LIMIT), "--json", "number,title"]));
if (open.length >= PR_LIMIT) {
console.error(`claim-check: ${open.length} open PRs hit the --limit ${PR_LIMIT} cap, so the list may be truncated.`);
console.error("claim-check: cannot prove a file is unclaimed from a partial list. Treat as UNKNOWN, not free.");
process.exit(2);
}
const hits = new Map(targets.map((t) => [t, []]));
for (const pr of open) {
/*
FNXC:FleetClaims 2026-07-31-21:10 (#3175 review — coderabbitai, "unreachable catch"): `gh()` exits on
failure, so this try/catch could never fire. Removed rather than made reachable: skipping a PR whose
diff failed is exactly how a claim goes unseen, and the fail-closed exit is already the right answer.
*/
const files = gh(["pr", "diff", String(pr.number), "--name-only"]).split("\n").filter(Boolean);
for (const t of targets) {
if (files.some((f) => f.includes(t))) hits.get(t).push(pr);
}
}
let claimed = false;
for (const t of targets) {
const prs = hits.get(t);
if (prs.length === 0) {
console.log(`UNCLAIMED ${t}`);
continue;
}
claimed = true;
console.log(`CLAIMED ${t}`);
for (const pr of prs) console.log(` #${pr.number} ${pr.title}`);
}
/* Exit 1 when anything is claimed, so a worker can gate on it: `... && start-work`. */
process.exit(claimed ? 1 : 0);