Skip to content

test(cli): the shared-name install-local jobs test lets each install run to the setup deadline, not a fixed 120 s - #25547

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25499-install-budget
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25499-install-budget

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Fixes #25499

Clause-②: no
Metadata-protocol: none

packages/cli/test/package-install-local-jobs-shared-name.integration.test.ts gave each of its three os package install children a fixed 120 s SIGKILL. That number was set on an idle machine. Under a full pnpm test on a 4 vCPU box, the first install outlived it, and 6 of 9 assertions went red against correct code. The beforeAll still had more than 390 s of its 600 s budget left.

Test-only. skip-changeset: the file is under packages/cli/test/, which @objectstack/cli's files[] does not ship.

What changed (one file)

  • An install has no budget of its own. beforeAll takes one deadline when it starts: SETUP_BUDGET_MS (the hook's existing timeout, 2 * BOOT_TIMEOUT_MS + 12 * RUN_WAIT_MS, unchanged at 600 s) less a 10 s KILL_REPORT_RESERVE_MS. Each install may run until that deadline. On a slow box, the installs use the time the boots and run waits did not need. No number went up.
  • A kill names the install. On the deadline the install's whole process group is SIGKILLed: the child now spawns detached, so the node process that tsx starts is killed too, and that process holds the pipes close waits on. The promise then rejects, naming the install, its wall time and the budget, with both transcripts. So the red is that message, not a bare exit null and not vitest's Hook timed out, which names no step.
  • The wall time is kept as evidence and is not an input. InstallRun now carries elapsedMs, and the first it puts it in its failure message.

Why there is no constant and no load factor: run-dev-unbuilt-workspace.e2e.test.ts in this package records two ceilings that failed this way, a fixed one and one scaled from a calibration. A bigger constant fails again on a busier box. The shared spawner helpers in test/helpers/serve-process.ts export no install budget and no load-aware ceiling (their only child budget is runServe's inline 180 s default). So this change adds no export to them, and the seven sibling package-install-local-* files keep their own copy of packageInstall() (see Acceptance notes).

Verification (head 02097ac15b, 4 vCPU container, every run under scripts/pm/os-verify-lock.sh)

leg result
the file alone (pure tree) Test Files 1 passed, Tests 9 passed, 87.10 s
per-install wall time, alone (one-off logging mutation, restored) alpha 11105 ms, beta 10159 ms, gamma 11483 ms, all exit 0
per-install wall time inside the whole cli integration tier, VITEST_MAX_WORKERS=3 (same mutation) alpha 14198 ms, beta 13516 ms, gamma 15747 ms, all exit 0; tier Test Files 171 passed, 1 skipped (172), Tests 1469 passed, 9 skipped, 2498.96 s
ablation: bogus artifact path (./dist/no-such-artifact.json) red: all three packages install fails with alpha (9838 ms): … Cannot read artifact: ENOENT, exit 1 against expected 0. 9/9 red, no hook timeout
ablation: budget spent (deadline = Date.now() + 1) red in 16.96 s: beforeAll rejects with os package install in …/shared_alpha was SIGKILLed after 7 ms: the setup budget (600000 ms for the whole beforeAll, less a 10000 ms reserve) was spent. The file fails and its 9 tests are skipped. No Hook timed out

The mutations went through scripts/ablation-replace.mjs, in WRAP mode, with an EXIT/INT/TERM trap. After the last leg the file's blob equals HEAD (bc1e7d9b3a…) and git diff HEAD is empty. My first attempt at the logging mutation did nothing: its anchor was a substring of its replacement, so the tool refused it (anchor count 1 -> 1). It was re-run with an anchor that is not.

NOT MEASURED: a full pnpm test on this box. The loaded reading above is the cli integration tier at three workers, not the turbo-wide run from the issue. The installs there took 14-16 s, against more than 120 s in the issue's run. So this box's load never came near the old ceiling, and the fix's tolerance of a busier box is argued from the design, not measured.

The local gates are listed in the report on #25499. pnpm lint and the cli typecheck / unit tier were run here.

Acceptance notes

  • The same fixed setTimeout(() => child.kill('SIGKILL'), 120_000) install ceiling is in six sibling files: package-install-local-{jobs,jobs-pull,hooks,handlers,boot-steps,uninstall-cleanups}.integration.test.ts. Not changed here, because the claim's file surface is this one file. No red is on record for them. carrier: none.
  • In this file, bootStart's fixed BOOT_TIMEOUT_MS still does not read the shared deadline. If installs used most of the budget, a hung second boot would show as Hook timed out instead of a named error. Not observed. carrier: none.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX

…up deadline, not a fixed 120 s

The three `os package install` children each had their own fixed 120 s
SIGKILL, set on an idle machine; under a full `pnpm test` on a 4 vCPU box the
first install outlived it and six assertions went red against correct code.
Each install now runs until the beforeAll's own deadline (its budget less a
10 s reserve), in its own process group, and a kill rejects naming the
install, its wall time and the budget instead of surfacing as a bare exit null.

Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4df174bcc48d694e654f90105ebd9e68d3545136 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:cli seat 1 (seat post #6024; lane read as domain:server) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-23T22:55Z · head 02097ac15b

item reading
Shape 1 file, +61 / −10 (diff read by the seat), test-only, skip-changeset. packageInstall() loses its own fixed 120 s SIGKILL. beforeAll takes one deadline when it starts (its existing 600 s timeout, now named SETUP_BUDGET_MS and unchanged, less a 10 s KILL_REPORT_RESERVE_MS), and each install may run until that deadline. The child spawns detached, so the kill reaches the whole process group, including the node process tsx starts. A kill rejects with the install's directory, wall time and the budget, so a hang reads as that message instead of exit null or Hook timed out. elapsedMs rides into the first it's failure message as evidence. Fixes #25499.
Read against the code An install that exits by itself still resolves whatever its code, and the first it judges it as before. The hook timeout is the same expression it was (2 * BOOT_TIMEOUT_MS + 12 * RUN_WAIT_MS). No shared helper moved: test/helpers/serve-process.ts exports no install budget.
Tests Alone: 9/9 in 87 s (installs 10–11 s each). The whole cli integration tier at 3 workers: 171 files passed, installs 13.5–15.7 s. Ablation: a bogus artifact path goes red at the install leg with the install's own error and no hook timeout; a spent budget (deadline = now + 1) makes beforeAll reject with the SIGKILL message in 17 s, no Hook timed out. Both restored to HEAD.
Dogfood battery / contract review Not owed: no ! in the title, test-only, no accept set moves.
Gates (check-runs on this head) 46 runs: 34 success, 12 skipped, 0 failed. mergeable_state: clean. Local: dispatch-gates 54 derived, 52 run exit 0; check:dual-build-cjs-loads and check:type-check-debt NOT MEASURED locally (no full build; lock turn), green in CI. pnpm lint exit 0. Report: mcp_calls 0; writes all through the relay.
Not measured, accepted A full turbo-wide pnpm test on a busy box was not rerun. Tolerance there follows from the design (the install now spends the hook's unused slack rather than a fixed ceiling), not from a reading.
Out of scope (noted, not filed) Six sibling package-install-local-{jobs,jobs-pull,hooks,handlers,boot-steps,uninstall-cleanups}.integration.test.ts files keep the same fixed 120 s install ceiling, with no red on record. In this file, the second boot's BOOT_TIMEOUT_MS does not read the shared deadline. Both are listed in the PR's Acceptance notes; a card is owed only if one goes red.
Landing Not governed (check-governed-merges.mjs --pr 25547, +61 / −10). Ready + auto-merge through the relay in this act.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 22:57
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 22:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit ab596f8 Oct 23, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25499-install-budget branch October 23, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants