fix(hooks): honor cancel during terminate wait - #12
Conversation
try? await Task.sleep in the post-timeout SIGTERM poll swallowed CancellationError, so a cancelled HookRunner.run reported timedOut and delayed SIGKILL. Use try await, SIGKILL on cancel, and rethrow. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed September 4, 2026, 3:58 AM ET / 07:58 UTC. ClawSweeper reviewWhat this changesUpdates Swabble’s shell-hook timeout cleanup to propagate cancellation, kill surviving hooks, and cover the behavior with regression tests and documentation. Merge readiness✅ Ready for maintainer review The prior blocker is resolved and no new blocking defect remains. This PR is still needed because current main retains the cancellation bug. Priority: P2 Review scores
Verification
How this fits togetherSwabble turns wake-word transcripts into configured shell commands. Its hook runner manages process completion and timeouts, returning success or an error to the daemon or an embedding application. flowchart TD
A[Transcript or library invocation] --> B[Hook runner]
B --> C[Configured shell process]
C --> D{Exit or timeout?}
D -->|Exit| E[Return process result]
D -->|Timeout| F[Terminate and briefly wait]
F --> G[Kill surviving process]
G --> H[Report cancellation or timeout]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Preserve ordinary timeout behavior while reliably reporting caller cancellation after cleanup, including when the child has already exited. Do we have a high-confidence way to reproduce the issue? Yes, from source: cancelling a timed-out invocation during its SIGTERM grace wait causes current main to suppress cancellation and report timedOut. This read-only review did not execute that path. Is this the best way to solve the issue? Yes. The cancellation catch and final checkpoint repair the identified cleanup paths while preserving the existing timeout sequence and public API. AGENTS.md: not found in the target repository. Codex review notes: model internal, reasoning high; reviewed against 0b8b05adbf17. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (8 earlier review cycles)
|
Cover cleanup that skips polling after the child exits, and wait for SIGTERM before cancelling in the live-process regression. Document the bounded cancellation contract. Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
Integrate the landed hook cancellation cleanup from #12 while retaining the reviewed Pages action pin and changelog entry. Combined Swift tests, docs checks, release CLI/library proof, and full P0-P2 review pass.
When a timed-out hook is being terminated, cancelling its
HookRunner.runtask could be swallowed and reported asHookRunnerError.timedOut. The runner now propagates cancellation from the SIGTERM grace wait, kills any surviving child, and checks cancellation after cleanup so an exited child cannot skip the cancellation checkpoint.The change keeps the existing timeout contract: uncancelled hooks receive SIGTERM, get up to 100 ms to exit, then receive SIGKILL if still running and report
.timedOut. Cancellation before the timeout still does not interrupt the initial process wait; this PR does not change that behavior or add CLI signal handling. The cleanup helper is internal for focused regression coverage; the public API is unchanged.The cancellation test now waits for a real SIGTERM marker before cancelling, and a second test deterministically covers cleanup with an already-exited child, where no polling sleep can observe cancellation. Removing the final checkpoint makes that regression fail with
.timedOut; restoring it passes.Validation on macOS 26.6.2 with Swift 6.4:
./scripts/format.sh: unchanged;./scripts/lint.sh: passed, six existing warnings.swift test --parallel: 18 tests passed.node --test scripts/build-docs-site.test.mjs: 2 tests passed.swift build -c release: passed.Swabblelibrary exercised the public API with a shell hook that records SIGTERM and keeps running. Cancelling during that grace wait returnedCancellationError; without cancellation it returned.timedOut. Both cases confirmed the child was no longer alive.No microphone session was needed for the changed process-cleanup behavior; live speech recognition was not exercised.
Original fix and cancellation regression by @SebTardif; maintainer follow-up completes the post-poll checkpoint, strengthens coverage, and documents the behavior. Related timeout work: #2.