mirror of
https://github.com/Yeachan-Heo/oh-my-claudecode.git
synced 2026-09-03 06:25:33 +08:00
* fix(windows): bound generic-hook runner timeout ownership and nested git (#3493) Native Windows generic-hook run.cjs parents survived 30-66 min despite 3-10s manifest budgets. Two runner-side gaps plus unbounded nested git calls left the generic hook path without reliable termination. run.cjs (generic branch): - Replace the blocking spawnSync generic dispatch with an async spawn child supervisor (runGenericChild) that owns a setTimeout and reaps the OS process tree while the child is still alive. spawnSync only returns after the child fully exits, so post-call cleanup was unreachable. - POSIX: spawn detached and process.kill(-pid, SIGKILL) the group with a direct-child fallback. Windows: taskkill /T /F /PID bounded by its own timeout:2000 so the reap cannot hang the handler. Job Object kill-on- close remains the documented escalation if the Windows PID-survival test regresses. - Always arm a runner-owned timer: resolveGenericTimeoutMs falls back to DEFAULT_GENERIC_TIMEOUT_MS (59500ms = max manifest budget - cushion) when manifest resolution is null (closes the unbounded Gap A) and is bypassed when the manifest resolves. - Pin the exit-code contract (result.status ?? 0 semantics): numeric exit propagates; signal-only, spawn error, reap failure, and timeout fail open with 0; single-resolve guard; timer cleared on every terminal path. - Add a require.main===module guard + module.exports so internals are unit-testable without executing dispatch. #3490 worker path, allowlist, provenance, and fail-open are unchanged. Nested git bounding: - Add shared BOUNDED_GIT_TIMEOUT_MS=2000 (scripts/lib + templates/hooks/ lib, byte-identical) and apply it at the 8 owned sites: the 4 unbounded resolveTranscriptPath git calls and the 4 over-budget 5000ms calls (resolveOmcRoot x2, code-simplifier x2). Already-bounded compliant sites (context-guard-stop 1000ms, workflow-drift-guard 2000ms) are untouched. Tests: - run-cjs-generic-timeout: exports/selection, POSIX group reap, A2 exit states, single-terminalization. - windows-prompt-hook-runner: generic-path Windows PID-survival reap assertion (runs on the windows-latest CI lane). - bounded-git-timeout-parity: both constant copies match. - windows-hide-hooks: two-tier static scan (every generic-hook git call bounded; owned sites must use the shared constant; compliant sites preserved) with independent-tier negative seeds. - installer: require bounded-git-timeout.mjs in templates/hooks/lib. Fixes #3493 * fix(windows): reap detached generic-hook tree on runner termination (#3493) Hostile review found that runGenericChild detached the hook into its own POSIX process group but reaped only from the inner timeout callback. If run.cjs was terminated or cancelled BEFORE its timer fired (outer hooks.json timeout, Ctrl-C, parent kill), the detached hook tree was orphaned - reintroducing the #3493 orphan class for the outer-termination path and regressing prior POSIX cancellation behavior. - Register SIGTERM/SIGINT/SIGHUP and process 'exit' handlers while the generic child is live; on runner termination reap the process tree (idempotent, single-terminal guard) before exiting fail-open. - Remove the handlers on every terminal path (timeout/exit/error) so no listeners leak across in-process supervisor calls. - reapTree now returns success/failure and inspects the Windows taskkill status. - Add a POSIX subprocess test that kills the runner before its timeout and asserts the detached child and grandchild are reaped. * fix(windows): guarantee runner exit on failed reap; non-blocking taskkill (#3493) Second hostile review found the timeout path could still leave run.cjs alive on Windows: reapTree returned false when taskkill was missing/denied/ timed out, but the live ChildProcess handle kept the event loop alive, so run.cjs never exited — the exact "run.cjs parent alive" symptom of #3493. - Add child.unref() on the timeout path so the runner ALWAYS exits fail-open even when the best-effort tree reap does not complete. - Make the Windows reap non-blocking: spawn `taskkill /T /F` detached + unref'd (fire-and-forget) instead of a synchronous spawnSync that could block up to 2000ms past the outer hooks.json budget. POSIX group-kill is already instantaneous. - Drop the now-unused spawnSync import. - Clarify the POSIX outer-cancellation test comment (Windows programmatic SIGTERM force-terminates rather than delivering a catchable signal, so that path is POSIX-specific; killing the grandchild proves the whole group, including the direct hook child, was reaped). --------- Co-authored-by: clawdbot <clawdbot@users.noreply.github.com>
5 lines
291 B
JavaScript
5 lines
291 B
JavaScript
// Budget-coherent PER-CALL ceiling for nested git calls in generic hooks.
|
|
// 2000ms < the smallest hook manifest budget (3s), so an inner git timeout
|
|
// fires before the runner's generic-execution timeout. Non-proportional by design (see #3493).
|
|
export const BOUNDED_GIT_TIMEOUT_MS = 2000;
|