|
| 1 | +# Break-test pass 8 - 2026-10-11 |
| 2 | + |
| 3 | +The break-test skill, run in a session pinned to the branch `arena/9aac6bbb-research-kit` |
| 4 | +at `195b2ca`. The pinned-branch rule applied: every probe ran in a throwaway clone under |
| 5 | +`/tmp/bt/`, never in the checkout. No code fix was applied (see F1), so the only commit is |
| 6 | +this report. |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +- **Stack and gate.** Node ESM, no dependencies and no `package.json`; kit 0.9.5. The gate, |
| 11 | + read from `.github/workflows/offline-suite.yml` (authoritative over the README): |
| 12 | + `selftest.mjs`, `preflight.mjs`, `examples/release-evidence/run-example.mjs`, the three |
| 13 | + Node<->Python conformance runners, `timeline.mjs` and `export-warc.mjs` regenerating the |
| 14 | + committed bytes, and an empty `git status --porcelain`. CI's seven jobs are |
| 15 | + `platform (ubuntu-latest, ubuntu-26.04, windows-latest)`, `node (24, 26)` and |
| 16 | + `archive tree`, aggregated into the required `suite` check. |
| 17 | +- **Environment.** Linux container, 2 CPUs, uid 1001 (non-root, `sudo` group), Node |
| 18 | + v22.22.3, git 2.39.5, Python 3.11.2, no Chromium or Chrome, only the locales `C`, |
| 19 | + `C.utf8` and `POSIX`, fd cap 1024. Differently from pass 7: `unshare -n` is still refused, |
| 20 | + but **`unshare -rn` works** (user namespace + network namespace), which makes the |
| 21 | + network-off probe pass 7 could not run possible here. Inside that namespace the loopback |
| 22 | + interface must be brought up (`ip link set lo up`) or every test that uses a local |
| 23 | + 127.0.0.1 server fails. |
| 24 | +- **Isolation.** Throwaway clones under `/tmp/bt/`: `fresh` (probe target), `p1`/`p2` |
| 25 | + (parallel runs), `deliv` (delivery diagnostics), `crlf` (autocrlf), a Hebrew-and-space |
| 26 | + path, a 272-character path, plus `/tmp/bt/browser` and `/tmp/bt/al2023libs` for an |
| 27 | + abandoned browser probe. The checkout itself was never modified, and |
| 28 | + `git status --porcelain` was empty before and after every run. |
| 29 | +- **Baseline.** Three unchanged full-suite runs in the real checkout: **1782 passed, 0 |
| 30 | + failed, failure set empty, 4 unsupported, exit 1** (an incomplete run is not a pass, |
| 31 | + ADR-0108), at 166.1 s / 169.5 s / 176.9 s, 87 test files, Node 22.22.3. The 4 UNSUP are |
| 32 | + the `browser-guard`/`browser-transport` LIVE tests, which need Chromium; this host has |
| 33 | + none. No test failed in any baseline run, and no flake appeared. |
| 34 | +- **Root gate, same head.** `preflight.mjs` PASS (0 blocking, 1 warning |
| 35 | + `hygiene/brief-unstamped`, 28 passing, `evidencePolicy=pluralist`); `handoff.mjs` OK (33 |
| 36 | + ledger entries, 27 evidence rows, chain verifies); `doctor.mjs` exit 1 with its one |
| 37 | + by-design blocker (`firecrawl-cli` not on PATH and no explicit keyless route declared; |
| 38 | + doctor prints the one-line fix); all four CI extra steps pass - release example 6/6, the |
| 39 | + three conformance pairs agree per vector, `TIMELINE.md (60 events)` and |
| 40 | + `55 records (27 captures)` regenerate, and the tree stays clean. |
| 41 | + |
| 42 | +## Findings |
| 43 | + |
| 44 | +> **[F1] `handoff` run from inside a project's `research/` folder says nothing has been |
| 45 | +> collected and tells the reader to collect it there** (Low, Possible) |
| 46 | +
|
| 47 | +Repro (any gated project with a collected corpus, e.g. a fresh clone): |
| 48 | + |
| 49 | +``` |
| 50 | +cd <project>/research |
| 51 | +node "$HOME/.agents/research-kit/bin/handoff.mjs" # exit 1 |
| 52 | +``` |
| 53 | + |
| 54 | +Observed: |
| 55 | + |
| 56 | +``` |
| 57 | +handoff: nothing to hand off yet - nothing has been collected in this project. |
| 58 | +
|
| 59 | +Collect it here, then commit research/ including research/raw/ and its dotfiles: |
| 60 | + .../research-kit/bin/research.mjs |
| 61 | +``` |
| 62 | + |
| 63 | +The parent directory is the project root and holds a complete corpus - `handoff.mjs` run |
| 64 | +there prints OK (33 ledger entries, 27 evidence rows, chain verifies). |
| 65 | + |
| 66 | +- **Root cause.** `bin/handoff.mjs` scopes everything to the cwd, which is the kit's |
| 67 | + contract ("the project is the current working directory"). From a subdirectory the four |
| 68 | + artifacts are absent, that is indistinguishable from an uncollected project, and the |
| 69 | + `nothingCollected` branch prints the collect-it-here remedy without checking whether the |
| 70 | + cwd is one of the project's own folders. |
| 71 | +- **Impact.** A misleading diagnosis whose advice points at collection, which spends |
| 72 | + credits (ADR-0129). The harm is bounded: the suggested command refuses in that directory |
| 73 | + - `research.mjs --dry-run` from `research/` prints `research/plan.json does not exist - |
| 74 | + nothing to collect` and exits 2 - so nothing is spent. `preflight.mjs` from the same |
| 75 | + folder prints `not gated - this project holds none of the four gate markers, so there is |
| 76 | + nothing to judge.` and exits 0 (the ungated scope notice, ADR-0153): the same |
| 77 | + subdirectory-scoped answer, without the wrong advice. |
| 78 | +- **Action: Not fixed, needs your decision.** The kit is feature-frozen (ADR-0117), and |
| 79 | + detecting "this cwd looks like a project's `research/` folder" is new behaviour, not a |
| 80 | + wording repair; it also changes user-facing text that tests cover. Recommended minimal |
| 81 | + change for the owner: when the cwd contains `raw/` and `EVIDENCE.md`, or |
| 82 | + `../research/raw/.fetches.jsonl` exists, print "this looks like the project's `research/` |
| 83 | + folder - run this from the project root" instead of the collect remedy. |
| 84 | +- **Verification.** The repro above, in a clone; the root-directory invocation still prints |
| 85 | + OK and is unchanged. |
| 86 | + |
| 87 | +## Applied fixes |
| 88 | + |
| 89 | +None. F1 is a frozen-surface diagnostic change and is reported for a decision instead. |
| 90 | + |
| 91 | +## Rejected fixes |
| 92 | + |
| 93 | +None attempted - no apply-and-revert cycle ran, because F1 is the only finding and its fix |
| 94 | +is not a break-test decision to take. |
| 95 | + |
| 96 | +## Remaining risks |
| 97 | + |
| 98 | +1. **The browser LIVE tests remain unverified here** (Medium, Unknown likelihood). They are |
| 99 | + the one place a real flake has been recorded: CI on 2026-10-11, the `node (24)` job's |
| 100 | + LIVE `browser-guard` meta-refresh test failed once - Chromium never made a request in |
| 101 | + 45 s - and passed on re-run and in every sibling job. Attempted and abandoned: an |
| 102 | + npm-sourced `@sparticuz/chromium` build was installed to `/tmp` (73 MB, its own |
| 103 | + `al2023` libraries extracted to satisfy `libnss3`/`libnspr4`), but its Lambda-target |
| 104 | + binary never dumped a DOM in this container (`--dump-dom about:blank` timed out at 45 s |
| 105 | + with a GPU-process crash loop), and it is not the Chrome CI uses in any case. |
| 106 | + Recommendation: run the two LIVE files about 50 times on a host with the CI browser and |
| 107 | + see whether the meta-refresh flake reproduces; whether that environment is worth |
| 108 | + providing is the owner's call. |
| 109 | +2. **A fresh collector without the Firecrawl CLI is not READY** (Low, Certain by design). |
| 110 | + `doctor.mjs` exits 1 and names the fix (install the CLI, or declare |
| 111 | + `transport: http-keyless` in the machine config). No action needed; recorded so a |
| 112 | + "1 blocker" reading is not mistaken for breakage. |
| 113 | +3. **Reports unindexed in `docs/README.md`** (Low, Certain). Pass 7 recorded this; this |
| 114 | + report's row is added in the same commit, and the older unindexed reports remain for the |
| 115 | + owner to index or leave. |
| 116 | + |
| 117 | +## Probe defects |
| 118 | + |
| 119 | +- The first network-off run was executed without `ip link set lo up`, so the tests that use |
| 120 | + local loopback servers failed (9 `FAIL` lines in `browser-guard`, `large-response` and |
| 121 | + `panel`) while a browser attempt also competed for the CPU. That run was discarded, and |
| 122 | + the probe was re-run in a namespace whose loopback was up: 1782 passed, 0 failed. The |
| 123 | + discarded run is not evidence about the kit. |
| 124 | +- One invocation was wrong by the operator, not the kit: `preflight.mjs` was invoked as |
| 125 | + `node research-kit/bin/preflight.mjs` from a project folder that does not contain the kit |
| 126 | + (the kit lives at its install path), which produced a `MODULE_NOT_FOUND` from Node. |
| 127 | + Re-run with the kit's absolute path, the verdict was the designed FAIL of an uncollected |
| 128 | + project (2 blocking) - the skill's step-1 reminder that the project is the cwd, not the |
| 129 | + kit's location. |
| 130 | + |
| 131 | +## Probes run |
| 132 | + |
| 133 | +Commands are from the root of a clone unless noted. Full-suite invocations used |
| 134 | +`RESEARCH_KIT_RESULT_FILE`; the unsupported count is reported rather than waived, because |
| 135 | +ADR-0108 treats an incomplete run as not-a-pass. The 27 rows below are an activity |
| 136 | +inventory, not 27 independent full-suite passes. |
| 137 | + |
| 138 | +| # | Probe | Command | Result | |
| 139 | +|---|---|---|---| |
| 140 | +| 1 | Baseline x3 | `node research-kit/bin/selftest.mjs` (real checkout) | 1782/0/4, exit 1, 166.1-176.9 s; failure set empty | |
| 141 | +| 2 | Root-in-userns control | `unshare -r node research-kit/bin/selftest.mjs` | 1782/0/4, 164.4 s - root changes nothing | |
| 142 | +| 3 | **Network off** | `unshare -rn sh -c 'ip link set lo up; node research-kit/bin/selftest.mjs'` | 1782/0/4, 162.5 s - the "runs offline" claim holds with no DNS and no sockets (pass 7's unreachable probe) | |
| 143 | +| 4 | Timezone | `TZ=Pacific/Kiritimati` full suite | 1782/0/4, 170.2 s | |
| 144 | +| 5 | Two suites at once | two clones, both full suites concurrently, 2 CPUs | both 1782/0/4, 175.9 s / 174.7 s | |
| 145 | +| 6 | Flakiness programme | 10x each: `concurrency`, `http-linear`, `proxy`, `large-response`, `panel`, `stdout-flush`, `browser-guard` | 0 FAIL in 70 runs; `browser-guard` exits 1 only because of its 4 UNSUP LIVE tests | |
| 146 | +| 7 | Clean checkout | fresh `git clone`, then `preflight.mjs` and `handoff.mjs` | exit 0 / exit 0 - nothing depends on untracked files | |
| 147 | +| 8 | CRLF / `core.autocrlf=true` | clone with `core.autocrlf true`, `git checkout-index -a -f`, then handoff/preflight | all files still LF; handoff OK; preflight PASS - the `.gitattributes` promise holds | |
| 148 | +| 9 | Missing capture | remove one `research/raw/*.md` in a copy, `handoff.mjs` | `handoff-capture-missing` names the evidence row and file; `handoff-chain-broken raw-missing`; collector remedy printed | |
| 149 | +| 10 | CRLF-smudged corpus | rewrite `research/raw/*.md` to CRLF, `handoff.mjs` | `handoff-chain-broken body-unmodified/line-endings ... differs only by CRLF line endings` - the ADR-0020 diagnosis, not a false "did not travel" | |
| 150 | +| 11 | HOME unset | `env -u HOME` doctor / preflight / handoff | exit 1 (the by-design blocker) / 0 / 0; no stack traces | |
| 151 | +| 12 | Minimal env | `env -i PATH=...` preflight / doctor | exit 0 / exit 1 (same blocker); no stack traces | |
| 152 | +| 13 | No git on PATH | `env PATH=<empty dir> node <abs>/doctor.mjs` | `fail git - git is not on PATH - the commit gate cannot install`, exit 1, fix printed; preflight still PASS | |
| 153 | +| 14 | Read-only project | `chmod -R a-w research/`, `preflight.mjs` | PASS - reads only | |
| 154 | +| 15 | Leaked git context | `GIT_DIR=<other repo>/.git` with preflight/handoff | correct verdicts (PASS / OK) - the CLIs are not redirected | |
| 155 | +| 16 | `RESEARCH_KIT_HOME` is a file | real `install.mjs` | exit 2, `could not write ... EEXIST`, target file untouched | |
| 156 | +| 17 | Read-only HOME | real `install.mjs`, `HOME` 0500 | exit 2, `could not write ... EACCES`; nothing left behind | |
| 157 | +| 18 | Hebrew + space project | `new-project.mjs "/tmp/bt/<hebrew> <space>" --topic "<hebrew>"`, `decompose.mjs`, `preflight.mjs` (kit by absolute path) | scaffold OK, decompose OK (MAP.md carries the Hebrew topic), preflight FAIL by design - 2 blocking on an uncollected project | |
| 158 | +| 19 | Long path | suite subset + handoff in a 272-character path | 10 passed / 0 failed; handoff OK | |
| 159 | +| 20 | fd cap | `ulimit -n 64`, suite subset | 2 passed / 0 failed | |
| 160 | +| 21 | Unicode TMPDIR | `TMPDIR=/tmp/bt/tmp-<hebrew>`, suite subset | 10 passed / 0 failed | |
| 161 | +| 22 | Panel port taken | listener on the port, then `panel.mjs --port <same>` | exit 2, `could not listen ... EADDRINUSE` - named, no stack | |
| 162 | +| 23 | Panel bad project | `panel.mjs --project /nonexistent` | exit 2, `<path> is not a folder` | |
| 163 | +| 24 | Subdirectory diagnostics | `preflight.mjs` / `handoff.mjs` from `research/` | F1 above: handoff's message is wrong for that cwd; preflight's "not gated" is the documented ungated scope | |
| 164 | +| 25 | CI extras | release example, three conformance pairs, timeline + export-warc, `git status` | 6/6 examples PASS; 3/3 pairs agree; 60 events and 55 records regenerate; tree clean | |
| 165 | +| 26 | Doctor / preflight / handoff at the head | the root gate | 1 blocker (by design), PASS 0 blocking, handoff OK | |
| 166 | +| 27 | Leftovers | `git status --porcelain` after every stage | empty - the suite and the probes wrote nothing into the checkout | |
| 167 | + |
| 168 | +## Not probed |
| 169 | + |
| 170 | +- **Windows and macOS.** No such host. CI covers Windows on every commit; macOS remains |
| 171 | + best-effort and untested in CI, as the workflow says. |
| 172 | +- **Real uid 0 outside a user namespace.** `unshare -r` maps the user to root *inside* a |
| 173 | + namespace; true container-root permission semantics were not covered, and the probe in |
| 174 | + row 2 showed no difference from the non-root baseline anyway. |
| 175 | +- **Locales other than `C`, `C.utf8` and `POSIX`.** None are installed. |
| 176 | +- **The four browser LIVE tests.** No Chromium or Chrome on this host; the npm-sourced |
| 177 | + build that was tried (see risk 1) never produced a DOM and is not the CI browser. |
| 178 | +- **The metered Firecrawl transport.** No key and no credits, and the skill forbids |
| 179 | + reaching paid services without consent; the collector ran keyless throughout. |
| 180 | +- **A real machine-wide install** (global `core.hooksPath`, `~/.claude/settings.json`). |
| 181 | + The skill forbids modifying the machine, so `install.mjs` ran with `HOME` or |
| 182 | + `RESEARCH_KIT_HOME` pointed at scratch paths, and the pre-commit hook was not registered. |
| 183 | +- **Node lines 24 and 26.** Only 22.22.3 is available locally; CI runs the other lines on |
| 184 | + every commit. |
| 185 | +- **Flakiness beyond the recorded programme.** 70 repeated subset runs and 7 full-suite |
| 186 | + passes found no flake; a multi-hour soak was not run. |
| 187 | +- **Git versions other than 2.39.5**, and a shallow clone's effect on the bundle/archive |
| 188 | + checks (the sandbox's own checkout is history-truncated, and `doctor`'s bundle row and |
| 189 | + the archive-tree job both passed anyway). |
| 190 | + |
| 191 | +## Summary |
| 192 | + |
| 193 | +The build is resilient at this head. Seven full-suite passes - three baselines, the |
| 194 | +root-in-userns control, the no-network run, a UTC+14 run and two simultaneous runs - all |
| 195 | +returned 1782 passed, 0 failed and an empty failure set, and 70 repeated runs of the |
| 196 | +timing-sensitive files produced no flake. Every deliberate break tried (no HOME, no git, |
| 197 | +read-only HOME and `research/`, a file where a folder belongs, a taken port, a missing |
| 198 | +capture, a CRLF-smudged corpus, Hebrew and 272-character paths, an fd cap of 64, a unicode |
| 199 | +TMPDIR) produced a named diagnostic instead of a crash; the two large gaps pass 7 had left |
| 200 | +open - no-network isolation and root semantics - are now closed. The single finding is a |
| 201 | +misleading `handoff` message when it is run from inside `research/` (Low; not fixed, |
| 202 | +because the freeze makes the fix a feature). The most important open risk is the browser |
| 203 | +LIVE tests, unverifiable here and the only place a flake has ever been recorded. Nothing on |
| 204 | +the branch changes kit code: review this one report commit, and the branch stays green. |
0 commit comments