From b8bfcd031b411974894cfdbbce849e993bd82cac Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 31 Jul 2026 08:10:29 -0700 Subject: [PATCH] tooling: answer "is this file claimed?" in one command (#3175) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 && 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) ## 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. --------- Co-authored-by: Claude Opus 5 (1M context) --- AGENTS.md | 30 +++++++++++ scripts/check-file-claimed.mjs | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 scripts/check-file-claimed.mjs diff --git a/AGENTS.md b/AGENTS.md index f233acb05f..5e276b00fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. + + + ### 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. diff --git a/scripts/check-file-claimed.mjs b/scripts/check-file-claimed.mjs new file mode 100644 index 0000000000..833a960059 --- /dev/null +++ b/scripts/check-file-claimed.mjs @@ -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 [...]"); + 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);