Skip to content

test(ci): bound dispatch fixture subprocess lifetime - #219

Merged
steipete merged 3 commits into
mainfrom
test/dispatch-fixture-supervision
Sep 3, 2026
Merged

test(ci): bound dispatch fixture subprocess lifetime#219
steipete merged 3 commits into
mainfrom
test/dispatch-fixture-supervision

Conversation

@steipete

@steipete steipete commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Bound the POSIX workflow-dispatch test fixture's subprocess lifetime without changing the workflow or production code.

The fixture used spawnSync("bash", ...) with no subprocess deadline. During local validation it stalled inside Node's synchronous process runner while Bash descendants waited, so Vitest's own timer could not interrupt it. That attempt required stopping the task-owned child and is preserved as a failed run. The underlying shell scheduling cause has not been conclusively identified.

The fixture now uses asynchronous spawn in a dedicated POSIX process group, a 10-second deadline, and a 1 MiB output bound. On failure it signals its owned process group once and awaits close before temporary-directory cleanup. The deadline stays active after the shell's exit event because a descendant can keep the pipes open. A timeout cannot be treated as success merely because the shell exited zero.

The behavioral test's outer budget covers its two sequential bounded fixture invocations plus cleanup margin; no repository-wide test timeout, production timeout, workflow timeout, or workload was changed. Windows retains the existing skip for the POSIX behavioral fixture.

Regression proof

  • Normal workflow serialization still verifies the exact PR ingress fingerprint, branch authority, and issue payload.
  • A real Node descendant holds inherited pipes open after Bash exits successfully and ignores SIGTERM. The test waits for readiness and the parent's exit, invokes the registered deadline callback, and verifies timeout failure plus closure after process-group SIGKILL. A real timer remains as a cleanup backstop.
  • A real child overflowing output is terminated and reaped; captured output stays bounded.
  • Group termination is idempotent, including when additional buffered output arrives after the first kill.

The subprocess supervisor and tests are local to this test file; no new exported helper or dependency is introduced.

Validation and limitations

  • Focused suite: 4 passed.
  • Complete serial suite: CI=1 pnpm test --maxWorkers=1 — 201 suites passed / 2 skipped; 7,082 tests passed / 80 skipped.
  • Build, boundary/size lint, documentation examples, pnpm pack:check, and git diff --check passed.
  • Codex autoreview: no accepted/actionable P0 findings in the final two-file patch.

Default parallel full checks did not pass locally and are not claimed as passing: separate runs hit the fixture deadline, a pre-existing sidecar process-exit test deadline, and varying archive test deadlines. The serial verification changed only test-file concurrency, not test bodies, internal stress concurrency, or product deadlines. All failed attempts remain in the campaign evidence. Initial exact-head hosted CI and first updated-base CI passed the normal repository configuration. The changelog conflicts are resolved. Current head 4178ed22d60f40bfc453cb80bb423d1826817ab0 is based on merged main 9edca2ca1e1708e9536587b221de739b439029e1; final exact-head CI is running. The supervisor itself is byte-identical to the captured runtime helper.

A later local serial run on the combined base also reached this fixture's deadline (7,107 passed / 1 timeout / 80 skipped), and a paired focused run alternated between passing and timing out. Those failures are retained too. This patch bounds and cleans up such failures; it is not claimed to eliminate or conclusively explain the underlying intermittent local shell stall.

Inspectable runtime capture

After-fix execution used the exact startFixture helper from committed head 408598672aa2936605c6aefcdfa93eced4063abb, extracted without semantic changes and stripped of TypeScript annotations by Node. Helper SHA-256: d6ecf91cbb24bb52a6bed8a8e2ff1d3e66e6454b811b4c0ed0e8a00221449ae2. Environment: physical macOS arm64, Node 24.20.0. Actual Bash/Node processes, the real 10,000ms deadline, and actual process-group signals were used—no mocked timer or signal.

Captured terminal results (only synthetic fixture state):

{"case":"normal","status":0,"signal":null,"timedOut":false,"error":null,"elapsedMs":17,"closeObserved":true,"stdoutBytes":11,"descendantGone":null,"scratchRemoved":true}
{"case":"exited-shell-live-descendant","status":0,"signal":null,"timedOut":true,"error":null,"elapsedMs":10005,"closeObserved":true,"stdoutBytes":27,"descendantGone":true,"scratchRemoved":true}
{"case":"output-overflow","status":null,"signal":"SIGKILL","timedOut":false,"error":"dispatch fixture output exceeded 1 MiB","elapsedMs":57,"closeObserved":true,"stdoutBytes":983040,"descendantGone":true,"scratchRemoved":true}

The second case confirms that a successful Bash exit is not mistaken for fixture completion while a live descendant holds the pipes. Its actual deadline fired at approximately ten seconds, closure was observed, the descendant was verified gone, and only then was scratch removed. The overflow case terminated before the deadline, retained less than 1 MiB, and likewise verified closure, descendant termination, and cleanup.

This is a separate validation repair discovered while verifying #217 and #218. Their lock implementations and frozen Windows proof candidate were left unchanged.

Replace unbounded synchronous fixture execution with async process-group supervision, an explicit deadline, bounded output, and close-before-cleanup. Cover descendants retaining pipes after a successful shell exit. The full serial suite passes; preserve separately recorded local parallel-run timeouts.
@steipete
steipete requested a review from a team as a code owner September 3, 2026 16:51
@clawsweeper

clawsweeper Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 3, 2026
@clawsweeper

clawsweeper Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed September 3, 2026, 1:26 PM ET / 17:26 UTC.

ClawSweeper review

What this changes

The PR replaces an unbounded synchronous Bash test fixture with an asynchronous POSIX process-group supervisor that limits runtime and captured output, with regressions for inherited-pipe descendants and output overflow.

Merge readiness

Ready for maintainer review

Keep open: current main still uses the unbounded synchronous fixture, while this exact head adds a narrowly scoped supervisor with sufficient real process-tree proof. The prior current-main refresh is incorporated; no introduced correctness or security defect was found.

Priority: P3
Reviewed head: 4178ed22d60f40bfc453cb80bb423d1826817ab0

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) A focused scope, real after-fix process-tree evidence, and targeted regression cases make this a solid test reliability patch.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (terminal): The changed production-adjacent owner is the local dispatch-test fixture supervisor. Supplied macOS terminal output exercises real Bash and Node descendants for normal completion, an exited shell whose descendant holds pipes until the real 10-second deadline, and a 2 MiB writer; it records closure, descendant disappearance, bounded output, and cleanup, and the helper is unchanged at the reviewed head.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The changed production-adjacent owner is the local dispatch-test fixture supervisor. Supplied macOS terminal output exercises real Bash and Node descendants for normal completion, an exited shell whose descendant holds pipes until the real 10-second deadline, and a 2 MiB writer; it records closure, descendant disappearance, bounded output, and cleanup, and the helper is unchanged at the reviewed head.
Evidence reviewed 4 items Current-main behavior remains unbounded: Fetched current main still invokes Bash through synchronous spawnSync without a fixture deadline or output cap, so the central repair is not already implemented.
Introduced supervisor and regressions: The PR uses a detached Bash process group, keeps its deadline until close, bounds combined captured output, and covers a live descendant holding pipes after Bash exits plus output overflow.
Proof maps to the reviewed head: The test supervisor is byte-identical between the commit named by the supplied macOS Bash/Node terminal capture and the reviewed head, so the capture applies to this PR version.
Findings None None.
Security None None.

How this fits together

The dispatch-workflow test runs the CI dispatch shell fragment against a stubbed GitHub client and verifies the resulting payload. Its fixture creates a shell process and temporary files; the new supervisor bounds that test-only process tree before cleanup.

flowchart LR
  A[Workflow shell fragment] --> B[Dispatch test fixture]
  B --> C[Shell process group]
  C --> D[Shell and descendants]
  D --> E[Bounded output capture]
  B --> F[Deadline or size limit]
  F --> G[Process-group termination]
  E --> H[Dispatch payload assertions]
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Change surface production +0/-0; tests +112/-19; changelog +1 All functional code changes are confined to one existing test fixture, with the required operational changelog entry.

Technical review

Best possible solution:

Merge the private POSIX fixture supervisor so descendant-held pipes become a bounded diagnostic failure, while retaining the existing Windows skip and unchanged CI dispatch behavior.

Do we have a high-confidence way to reproduce the issue?

Yes, source-reproducible: current main runs the fixture synchronously without a deadline, and the supplied real Bash/Node capture demonstrates the descendant-held-pipe condition the new supervisor bounds.

Is this the best way to solve the issue?

Yes: keeping process-group termination, the timeout, and output cap local to this fixture fixes the hang without changing the workflow or production package.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 9edca2ca1e17.

Labels

Label justifications:

  • P3: This is a focused test-fixture reliability improvement and does not change package or CI-dispatch behavior.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The changed production-adjacent owner is the local dispatch-test fixture supervisor. Supplied macOS terminal output exercises real Bash and Node descendants for normal completion, an exited shell whose descendant holds pipes until the real 10-second deadline, and a 2 MiB writer; it records closure, descendant disappearance, bounded output, and cleanup, and the helper is unchanged at the reviewed head.
  • proof: sufficient: Contributor real behavior proof is sufficient. The changed production-adjacent owner is the local dispatch-test fixture supervisor. Supplied macOS terminal output exercises real Bash and Node descendants for normal completion, an exited shell whose descendant holds pipes until the real 10-second deadline, and a 2 MiB writer; it records closure, descendant disappearance, bounded output, and cleanup, and the helper is unchanged at the reviewed head.

Evidence

What I checked:

Likely related people:

  • Martin Cleary: Raw commit 7a0d57e adds test/clawsweeper-dispatch-workflow.test.ts:89 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 7a0d57ef5948; files: test/clawsweeper-dispatch-workflow.test.ts)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (2 earlier review cycles)
  • reviewed 2026-09-03T16:57:01.643Z sha 4085986 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-03T17:17:17.189Z sha f3c88b4 :: blocked before merge. :: none

Resolve the changelog insertion while preserving both entries. The supervisor is unchanged from its reviewed runtime capture. Keep the separately recorded local fixture timeout as a failure; refreshed hosted CI remains the landing gate.
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 3, 2026
Bring the unchanged reviewed supervisor onto current main. Preserve local timing failures and rely on fresh hosted validation before landing; no workflow or production behavior is changed by this PR.
@steipete
steipete merged commit 8739325 into main Sep 3, 2026
26 checks passed
@steipete

steipete commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Landed as 8739325; merged tree b5d5d95415929c177fb9e765250884b99599df33 matches reviewed head 4178ed22d60f40bfc453cb80bb423d1826817ab0. The checkout was fast-forwarded to main and verified clean.

Reexecuted pnpm test test/clawsweeper-dispatch-workflow.test.ts --reporter verbose from actual merged main: 4/4 passed, including normal payload serialization, a descendant retaining pipes after Bash exits, and output-overflow cleanup. git diff --check passed. Final exact-head CI, initial CI, and first updated-base CI passed. Final Codex autoreview was scoped-clean at P0; exact-head ClawSweeper reported no correctness/security findings.

The runtime capture in the PR body exercised the unchanged committed helper with real macOS arm64 / Node 24.20.0 processes, actual process-group signals, and the real ten-second timer. The shell-exited/live-descendant case timed out at 10,005 ms, observed closure, verified the descendant gone, and removed scratch. The overflow case terminated at 57 ms with 983,040 captured bytes, below the 1 MiB limit. Windows retains the existing skip for this POSIX behavior fixture.

The standalone full serial suite passed 7,082 / 80 skipped, and build/docs/boundary/package checks passed. Local parallel attempts and a later combined-base serial attempt did not all pass: the latter recorded 7,107 passed / 1 fixture timeout / 80 skipped. Those failures remain in the campaign evidence. This repair bounds and cleans up stalled fixtures; it does not claim to eliminate or conclusively identify the underlying intermittent local Bash stall. After an initial quota error, the existing GitHub cache returned the exact merged-main results: ci, coverage, and CodeQL all completed successfully at 8739325c706dc91cb97256c8d821363441c6feab.

No production code, workflow, dependency, global test timeout, release, or registry publication changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant