test(#4515): migrate installer-runs batch to named timeout constants (#4575)

* test(#4515): migrate installer-runs batch to named timeout constants

Batch 4 of the ad hoc timeout literal migration (epic #4445). Replaces
every bare numeric timeout/timeoutMs object-literal property across
tests/install.test.cjs, tests/install-minimal-hooks.test.cjs,
tests/fragment-single-edit-propagation.install.test.cjs,
tests/install-regressions.test.cjs, tests/install-runtime-artifacts.test.cjs,
tests/npm-integrity-gate.test.cjs, tests/faulty-deps.test.cjs,
tests/release-tarball-smoke.install.test.cjs,
tests/install-write-confinement.test.cjs, tests/plugin-manifest.test.cjs,
and tests/packaging-shipped-scripts-require-only-shipped.test.cjs with a
named constant, per eslint-rules/no-adhoc-timeout-literal.cjs. Removes
these 11 files from the rule's allowlist.

Adds one new shared constant to tests/helpers/timeouts.cjs for a
fixture-JSON value (in seconds, not ms) mimicking a Claude Code
settings.json hook-entry's own timeout field, shared across two files in
this batch. Every other new constant is file-local, each carrying a
comment explaining why its call site is a distinct operational class from
the existing shared norms even where its digits numerically coincide with
one. No src/bin file touched, no numeric timeout value changed anywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: combine gsd-worktree-path-guard's git rev-parse calls to cut subprocess count under CI load

Discovered while verifying PR #4575 (a full test (windows-latest) failure in an unrelated test, tests/kilo-upgrades.test.cjs's worktree-path-guard rejection test). Root cause: this hook's up-to-4 sequential git spawns (git-dir, branch, worktree-toplevel, file-toplevel), invoked inside a native-plugin's own 8000ms-capped subprocess wrapper, left zero margin for node/git startup overhead — the guard is deliberately fail-open on any timeout, so CI-load-induced latency in this chain silently downgrades a security block into an allow. Combines the first three git rev-parse calls (git-dir, branch via --abbrev-ref, worktree-toplevel) into ONE spawn instead of three, using git's documented multi-flag rev-parse output (one line per flag, in order) — verified empirically including the detached-HEAD edge case. No timeout value changed; this reduces subprocess COUNT, not any tolerance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs: add changeset fragment for the worktree-path-guard fix

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-09-09 11:45:44 -04:00
committed by GitHub
parent 5823d2ec7a
commit 30468f16fe
15 changed files with 221 additions and 97 deletions

View File

@@ -32,6 +32,20 @@ const { splitLines, joinLines } = require('../gsd-core/bin/lib/text-lines.cjs');
const { createTempDir, cleanup, writePackageSourceMarkerFixture } = require('./helpers.cjs');
const { runNode } = require('./helpers/process-seam.cjs');
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
// Bound the writer subprocess so a regression that hangs the writer
// (or the dispatcher) cannot deadlock CI (PR #3003 CR feedback).
// 30s is generous for what should complete in <1s; if it trips,
// surface that as a clear test failure rather than CI hanging.
const WRITER_SUBPROCESS_TIMEOUT_MS = 30_000;
// Bounds a real `gsd-tools.cjs capability install` subprocess run against a
// fully-installed tree; digits coincide with the shared
// HOOK_FANOUT_TIMEOUT_MS/SEAM_DEFAULT_TIMEOUT_MS constants but this is
// neither a hook fan-out nor an omitted-default seam call, so kept as its
// own constant.
const CAPABILITY_INSTALL_TIMEOUT_MS = 60000;
const {
INSTALL_SCRIPT,
MANIFEST_NAME,
@@ -5564,11 +5578,9 @@ describe('Bug #2973: dev-preferences default writer path is skills/gsd-dev-prefe
// landed in the developer's live config dir instead of tmpHome.
env: Object.assign({}, process.env, TEST_ENV_BASE, { HOME: tmpHome, USERPROFILE: tmpHome }),
encoding: 'utf-8',
// Bound the subprocess so a regression that hangs the writer
// (or the dispatcher) cannot deadlock CI (PR #3003 CR feedback).
// 30s is generous for what should complete in <1s; if it trips,
// surface that as a clear test failure rather than CI hanging.
timeout: 30_000,
// See WRITER_SUBPROCESS_TIMEOUT_MS's own doc comment (top of file)
// for the full rationale.
timeout: WRITER_SUBPROCESS_TIMEOUT_MS,
});
assert.equal(result.signal, null,
`writer subprocess was killed by signal ${result.signal} (likely timeout): ${result.stderr}`);
@@ -6948,7 +6960,7 @@ function realInstall() {
const res = spawnSync(
process.execPath,
[INSTALL, '--claude', '--global', '--config-dir', dir],
{ encoding: 'utf8', timeout: 120000, env: childEnv },
{ encoding: 'utf8', timeout: INSTALL_TIMEOUT_MS, env: childEnv },
);
assert.strictEqual(res.status, 0, `install --claude failed: ${res.stderr || res.stdout}`);
return dir;
@@ -6992,7 +7004,7 @@ describe('Gap 1 (end-to-end CLI): installed capability install uses the real hos
const res = spawnSync(
process.execPath,
[installedTools, 'capability', 'install', src, '--scope', 'global', '--yes', '--json'],
{ cwd, env, encoding: 'utf8', timeout: 60000 },
{ cwd, env, encoding: 'utf8', timeout: CAPABILITY_INSTALL_TIMEOUT_MS },
);
const combined = `${res.stdout || ''}\n${res.stderr || ''}`;
assert.doesNotMatch(