LearnNewsExamplesServices
Frontmatter
id15878
titlelint-tree-json spec has 3s of headroom over a 27s lint — it times out under any load
stateClosed
labels
bugaitesting
assigneesneo-opus-ada
createdAtJul 25, 2026, 1:17 AM
updatedAtJul 25, 2026, 2:41 PM
githubUrlhttps://github.com/neomjs/neo/issues/15878
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 25, 2026, 2:41 PM

lint-tree-json spec has 3s of headroom over a 27s lint — it times out under any load

Closed Backlog/active-chunk-9 bugaitesting
neo-opus-ada
neo-opus-ada commented on Jul 25, 2026, 1:17 AM

Context

Surfaced while probing #15861's workers:4 re-land. It failed in the wide run, so the first question was whether parallelism caused it. It does not — and proving that is what exposed the real defect.

Corrected 2026-07-25 — this body originally named ONE affected test; there are TWO. @neo-gpt-emmy falsified the census during review of PR #15881: lintTreeJson.spec.mjs:63 shells out to the same script over the same tree and is a cost twin, left on the default 30s by the first cut of the fix. The original framing is folded in place rather than appended, because peers were asked to build against it. The failure was population, not value — the measurement below was always correct; the set it was applied to was not.

The Problem

Two tests in test/playwright/unit/ai/scripts/lint/lintTreeJson.spec.mjs lint the real learn/ tree, reaching the same full walk through different seams. Both fail with Test timeout of 30000ms exceeded:

Test Seam Measured (isolated)
:63"CLI: the real learn/tree.json passes" spawnSync shell-out — process boundary + exit code 29.42s wall / 27.07s user
:271"runLint: exported entry returns a numeric exit code on the real tree" module export, no process boundary 29.63s wall / 27.15s user

Only :271 was named in the original report, because it was the one that surfaced by name in the failing run. :63 carries the identical defect and was found by enumerating the call sites of the expensive primitive rather than trusting the failure's label.

It fails at --workers=1, alone, with nothing else running. So it is not contention and not a parallelism-exposed isolation defect.

Measured directly:

$ time node ai/scripts/lint/lint-tree-json.mjs
25.71s user  1.70s system  101% cpu  27.056 total
exit=0

The lint genuinely takes ~27s. The spec's timeout is 30s. That is roughly 3 seconds — ~11% — of headroom. Any load at all (a parallel suite, a busy CI runner, a cold cache) pushes it over. It is not hanging; it has no margin.

The lint itself is healthy: exit 0, zero violations. The only broken thing is the budget it is given.

The Architectural Reality

  • ai/scripts/lint/lint-tree-json.mjs — walks learn/ and cross-checks learn/tree.json; the ~27s is real work over the whole docs tree.
  • lintTreeJson.spec.mjs:63 and :271 — the two tests in the file that reach the real tree, one through the CLI and one through the module export. Every other test in the file uses fixtures and finishes in milliseconds.
  • Playwright's default 30s per-test timeout applies; nothing in the spec raises it.

The Fix

Raise both real-tree tests' timeout to a value with genuine headroom, and record the measurement inline so the number is traceable rather than cargo-culted.

Because the failure was population rather than value, the remedy should make the population the obvious thing: one documented REAL_TREE_TIMEOUT const shared by both sites, not two literals. A third real-tree test should then find one thing to reach for instead of a comment to re-read.

This is not weakening a test. The assertions are exitCode === 0 and violations === []; both are untouched and keep their full discriminating power. The 30s figure is an inherited default, not a property under test — the spec is not "the lint finishes within 30s". If it were, it should say so explicitly and be justified, which is a different ticket.

The slowness stays visible rather than being hidden by a bigger number: the inline comment carries the measured duration and the date, so the next reader sees a 27s lint rather than an arbitrary constant.

Acceptance Criteria

  • Both real-tree tests (:63 and :271) pass reliably at --workers=1 and inside a full --workers=4 run
  • The new timeout is justified inline with the measured duration of both twins, not a round number pulled from nowhere
  • exitCode / violations assertions unchanged — no reduction in what either test discriminates
  • Scoped to those two tests, not the file or the project (the fixture-based tests are fast and keep the strict default)
  • #15874's failure set drops this entry, so its remaining failures are unambiguously isolation-related

Out of Scope

  • Making lint-tree-json faster. ~27s over the docs tree may be perfectly reasonable, or may be optimisable — either way that is a performance ticket with its own measurement, not a timeout fix.
  • Raising the project-wide timeout. The other tests in this file are fixture-based and fast; a blanket raise would hide future regressions in all of them.
  • #15874's isolation defects. Separate cause, separate ticket; this one exists partly so those stop being confounded by it.

Avoided Traps

  • Calling it flake and retrying. It reproduces alone, deterministically. A retry would have masked a measurable budget problem.
  • Assuming parallelism because it appeared in a parallel run. The serial re-run is what separated the two, and it is why #15874 can now be scoped honestly.
  • Inferring the population from the failure's name — NOT avoided; this ticket walked into it. The first cut measured :271 precisely, fixed :271, and never enumerated the other call sites of the same expensive lint. The file's own header already named the real learn/tree.json in a list that had been read. Caught by @neo-gpt-emmy at review, not by the author. Recorded because the care invested in the method is exactly what stopped the scope being questioned: a rigorous instrument pointed at the wrong population still authors a confident wrong answer, and unlike a sloppy one, nothing about it feels unfinished.
  • Raising the timeout without measuring. A number chosen to make red go green is the trap; a number chosen from a measured 27s with recorded provenance is not.

Related

#15874 (the isolation defects this was confounding) · #15861 (the re-land probe that surfaced it) · #15783.

Handoff Retrieval Hints

  • query_raw_memories("lint-tree-json 27s timeout 30s headroom fails alone not parallelism")
  • Reproduce with: time node ai/scripts/lint/lint-tree-json.mjs

Live latest-open sweep: checked at 2026-07-24T23:19Z against the open queue and a title scan for tree-json|timeout — no equivalent found. A2A sweep: no [lane-claim] on this scope.