LearnNewsExamplesServices
Frontmatter
id17184
titleThe fixed-sleep guard parses 1,036 specs to inspect 109, and can be SIGKILLed
stateClosed
labels
bugaiperformancebuild
assigneesneo-opus-grace
createdAtAug 15, 2026, 5:20 PM
updatedAtAug 15, 2026, 7:40 PM
githubUrlhttps://github.com/neomjs/neo/issues/17184
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 15, 2026, 7:40 PM

The fixed-sleep guard parses 1,036 specs to inspect 109, and can be SIGKILLed

Closed Backlog/active-chunk-16 bugaiperformancebuild
neo-opus-grace
neo-opus-grace commented on Aug 15, 2026, 5:20 PM

Resolves nothing else — this is a regression I shipped in #17126 today, surfaced by @neo-opus-vega.

Problem

check-fixed-sleeps.mjs moved candidate discovery from a line-regex to an acorn AST walk (#17126, merged 69057410d6). That fixed three real parse-form escapes and was the right call. It also made the guard 7× slower and 3× hungrier, and @neo-opus-vega hit the consequence within the hour:

[FAILED] node ./buildScripts/util/check-fixed-sleeps.mjs [SIGKILL]
✖ Task killed: node ./buildScripts/util/check-fixed-sleeps.mjs

CORRECTED — the diagnosis above was wrong, and I falsified it by accident while landing the fix. It is not a resource ceiling. lint-staged kills in-flight tasks when a sibling task fails, and reports that cancellation in the same shape as a crash. Reproduced twice in one session, with two different failing siblings:

[FAILED] node ./buildScripts/util/check-ticket-archaeology.mjs [FAILED]
[STARTED] Reverting to original state because of errors...
[FAILED] node ./buildScripts/util/check-fixed-sleeps.mjs [SIGKILL]
[FAILED] node ./buildScripts/util/check-block-alignment.mjs --staged [FAILED]
[STARTED] Reverting to original state because of errors...
[FAILED] node ./buildScripts/util/check-fixed-sleeps.mjs [SIGKILL]

The revert message precedes the kill in both. @neo-opus-vega's "passed on retry with a smaller staged set" fits exactly: the smaller set did not trigger the failing sibling. The guard is the longest-running task in the set, so it is the one most often still in flight when a sibling fails — which is why it looks singled out.

This changes what the ticket is. The cost work below stands entirely on its own measurements (7x wall, 3x memory, all self-inflicted this morning) and is worth doing regardless. It is not, and never was, the fix for the SIGKILL. Being cheaper only shortens the window in which the guard can be caught in flight.

Measured, on dev at 69057410d6

version wall peak RSS correct?
pre-AST regex (7e35cd9c7e^) 0.14 s 76 MB no — found 1 of 4 legal spellings
shipped AST 0.85 s 234 MB yes
AST + the two fixes below 0.31 s 156 MB yes, byte-identical output

The regex row is not a target — it was wrong, which is why the AST landed. The comparison that matters is correct-and-expensive versus correct-and-cheap.

Root cause: two avoidable costs, both mine

1. It parses every spec to inspect a tenth of them. The walk runs over all 1,036 unit specs. Only 109 contain the token setTimeout at all. fixedWaitMs() requires an Identifier callee named setTimeout, so the literal token must appear in source — no token, no call, nothing to parse. A source.includes('setTimeout') pre-filter skips 927 files with no change in verdict.

2. locations: true inflates every node in the tree, and the guard needs a line number only for the handful of nodes that actually match. Dropping it and deriving the line from node.start at match time is where the memory goes.

Neither is a design change; both are the same walk doing less work.

Equivalence is proven, not assumed

The optimized version was diffed against the shipped one over the whole tree — all 40 sites and 3 backlog entries identical, including line, which matters because text and line feed the baseline key. A faster guard that renumbered sites would silently invalidate every grandfathered row.

The part that is not performance

@neo-opus-vega's framing is the durable half and I want it kept as its own criterion: a guard that can be killed is a guard that can silently not run, and lint-staged surfaces a SIGKILL in the same shape as an ordinary failure. Reducing the ceiling makes a kill less likely; it does not establish what happens when one occurs. That needs checking rather than assuming — the plausible reading is that both lint-staged and CI fail closed on a killed process, but "plausible" is exactly the standard this guard exists to refuse.

Out of Scope

  • Reverting to the regex matcher. It was wrong in three measured ways; #17126 documents them.
  • The callback-form widening (#17177), which will increase the matched-site count and interacts with this only in that both touch fixedWaitMs().
  • Tuning lint-staged concurrency. The guard should be cheap enough not to need it.

Acceptance Criteria

  • A source-token pre-filter skips files that cannot contain a setTimeout call, and a spec pins the equivalence: the pre-filtered walk and the unfiltered walk return identical sites and backlog over a fixture containing both matching and non-matching files.
  • The parse no longer requests locations; the reported line is derived at match time and a spec asserts the exact line for a match deep in a file (a wrong derivation silently rekeys the baseline, so this is a correctness test, not a perf test).
  • Wall time and peak RSS are recorded in the PR body against the 69057410d6 baseline, on the same tree, so the claim is a measurement rather than an adjective.
  • The full-tree verdict is proven unchanged: sites and backlog identical to the shipped guard, line included.
  • The kill path is established rather than assumed — determine what lint-staged and the CI job do when the guard is killed rather than exiting non-zero, and if either can report anything other than failure, that is the finding and gets its own leaf.

Evidence class

Measured on dev at 69057410d6, 2026-08-15: /usr/bin/time -l for both wall and max RSS, three configurations, same tree; file counts by find test/playwright/unit -name '*.mjs' (1,036) and grep -rl setTimeout (109); equivalence by diffing the two implementations' full site/backlog output in one process. Live incident reported by @neo-opus-vega on vega/15861-workers-4-reland, one occurrence, passing on retry with a smaller staged set.

Live latest-open sweep: latest 10 open issues at 2026-08-15T15:20:12Z — #17177 is the adjacent guard ticket (detection scope, not cost) and nothing covers this. A2A sweep: @neo-opus-vega flagged rather than filed and handed it to me explicitly.

Decision Record impact

none.

🖖 Authored by Grace (Claude Opus 5, Claude Code). Session b17338dd-b474-494f-b08c-683044de2ddb. Regression self-reported: I shipped the AST walk this morning and did not measure its cost.

tobiu referenced in commit 2176d69 - "perf(build): the sleep guard parses the files that can match, not all of them (#17187) on Aug 15, 2026, 7:40 PM
tobiu closed this issue on Aug 15, 2026, 7:40 PM