Two suites could hang forever and never print a tally, because the one
mechanism that could interrupt the wedged work was missing on both paths.
Hang 1 (bun run test / build-phase.test.mjs): the test's run() helper
spawned every child with spawnSync and no timeout. spawnSync blocks the
test worker's thread, so node's --test-timeout (an event-loop timer)
cannot interrupt a child that wedges (a fork/exec blocked on OS resources
under concurrency, a gate's comp-diff grandchild, or a stray browser
launch). Bound every child with spawnSync timeout + killSignal SIGKILL so
a wedge becomes a fast, named failure the next test survives.
Hang 2 (bun run test:skill-behavior): runTurn called generateText with no
client-side deadline, so a stalled provider stream kept the fetch (and the
whole node process) alive past the per-test timeout, producing no tally.
Attach a real AbortSignal (default 840s, under the 900s per-test cap):
on expiry the fetch aborts, the turn throws, and the scenario
fails-and-continues. The unref'd timer is cleared on completion.
Runner backstops: run-tests.mjs now spawns each command as a detached
process-group leader and enforces a per-suite wall-clock cap that SIGKILLs
the entire group (workers, grandchildren, browsers) on expiry, with
SIGINT/SIGTERM forwarded so Ctrl-C still reaps the tree. The core node
batch gets a finite --test-timeout (180s); skill-behavior gets a 60min
group cap. Env overrides: IMPECCABLE_TEST_WALL_CLOCK_MS,
IMPECCABLE_SKILL_BEHAVIOR_TURN_TIMEOUT_MS,
IMPECCABLE_BUILD_PHASE_RUN_TIMEOUT_MS.
Proof: bun run test green twice (~60s); scoped claude-sonnet-5
skill-behavior sweep terminates with a tally (20 tests, ~32min) where the
840s abort caught a wedged redesign turn and the sweep continued instead
of hanging.
Prepared with AI assistance (Claude Code).
* test: harden the test strategy (triggers, runner speed, release guards)
Follow-ups from an end-to-end testing strategy review:
- Suite triggers are now auto-generated from each suite's own file list,
so change-based CI can never miss a test file again (four files were
unreachable by their own edits, and tests/lib/detector-bundle.test.js
triggered core while running in detector). Two new meta-tests pin the
invariant. Hand-written trigger patterns now carry only source paths
and fixture dirs; palette dropped from the live triggers since no
suite tests it.
- The node runner batches all files into one node --test invocation at
concurrency 4 instead of spawning per file. Default suite drops from
~159s to ~100s; the live suite soaked clean three times.
- scripts/release.mjs gets its first tests: 12 scenarios spawning the
real script inside a disposable git repo with a local bare origin,
covering every refusal guard plus notes/tweet rendering, all under
--dry-run.
- skill/scripts/live/ui-core.mjs deleted: zero references repo-wide,
superseded by the July live rewrite, yet still shipping to users.
cli/lib/download-providers.js annotated with its cross-repo consumers
(impeccable-site Pages Functions) so it is not mistaken for dead code.
- CLAUDE.md gains an area-to-suite table for the opt-in suites a change
owes; AGENTS.md syncs the plugin-e2e commands and obligations.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* fix: exclude peeled tag lines from release-test origin cleanup
Copilot: git ls-remote --tags emits ^{} peel lines for annotated tags,
which are not deletable refs; --refs filters them so the cleanup loop
survives a future scenario that pushes an annotated tag.
AI-assisted via Claude Code under maintainer direction.
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
* Improve CI test coverage
* Stabilize live E2E harness
* Shard live E2E CI
* Cache live E2E CI dependencies
* Stabilize live E2E smoke CI
* Update generated live browser bundles
* Tighten live E2E smoke runtime
* Prevent live E2E smoke hangs
* Stabilize live E2E CI coverage
* Fix stale accept DOM cleanup
* Regenerate live browser outputs