Skip to content

fix(cli): bound the --notify hook through the shared runner#47

Merged
agjs merged 1 commit into
mainfrom
fix/notify-unbounded-hang
Jun 23, 2026
Merged

fix(cli): bound the --notify hook through the shared runner#47
agjs merged 1 commit into
mainfrom
fix/notify-unbounded-hang

Conversation

@agjs

@agjs agjs commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What

Harness-review pass over the lib/fs subsystem (shared command runner, scope, fs helpers). One verified P2 fixed, plus manifest staleness.

Fix — --notify could hang the run forever (P2)

runNotify (cli.ts) spawned Bun.spawn(["sh","-c",cmd]) then await proc.exitedno timeout, no abort — bypassing the shared runner whose entire purpose is uniform kill-timeout (manifest invariant #1 for lib/fs).

Reproduced: runNotify("sleep 3") blocks the full ~3s. A notifier that genuinely hangs (a curl to a dead host without --max-time, a stray read on stdin) wedges the run forever — and it fires at the end of a multi-hour greenfield build, the worst possible place. This contradicts its own docstring ("best-effort: a failing or missing notifier never changes the run's exit code" — an infinite hang means there is no exit code).

Changes

  • Add an env option to runShellCommand/runArgvCommand (REPLACES inherited env, like Bun.spawn) — the runner had none, and the notifier needs $TSFORGE_STATUS.
  • Route runNotify through runShellCommand with a 30s NOTIFY_TIMEOUT_MS, forwarding output via onChunk. Timeout is an optional param for testability.
  • docs(manifest): fix stale lib/fs paths (scope.tsscope/scope.ts); document the --notify route, env semantics, and the one deliberate non-runner spawn (the long-lived MCP stdio transport).

Tests

  • process.test.ts: shared runner passes a custom env to the child.
  • cli.test.ts: runNotify is timeout-bounded — flip-confirmed (reverting to the unbounded spawn blocks to the test ceiling).
  • Full gate green: 1425 pass, 0 fail.

Verified clean this pass

group-kill / no leaked & child; bounded output drain; missing-binary→127; no other bypassing spawn sites; add_dependency argv form; scope traversal rejection.

runNotify spawned `sh -c cmd` with a bare `await proc.exited` — no timeout, no
abort — bypassing the shared command runner whose whole purpose is uniform
kill-timeout. A notifier that hangs (curl to a dead host with no --max-time, a
stray `read` on stdin) wedged the run forever at the finish line of an
unattended/cron greenfield build, contradicting its own docstring ("best-effort:
never changes the run's exit code" — an infinite hang means no exit code at all).

- Add an `env` option to runShellCommand/runArgvCommand (REPLACES inherited env),
  so a command routed through the runner can still inject $TSFORGE_STATUS.
- Route runNotify through runShellCommand with NOTIFY_TIMEOUT_MS (30s), forwarding
  output via onChunk. Timeout is an optional param for testability.
- Tests: shared-runner custom env; runNotify is timeout-bounded (flip-confirmed).
- docs(manifest): fix stale lib/fs paths (scope.ts -> scope/scope.ts), note the
  --notify route + env semantics + the one deliberate MCP-transport spawn exception.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the --notify hook to route through the shared runner (runShellCommand), ensuring that notifications are bounded by a 30-second timeout and do not hang indefinitely. It also adds support for passing custom environment variables to spawned processes and includes corresponding tests. The review feedback suggests two improvements: sanitizing the custom env object to filter out undefined values before passing it to Bun.spawn to avoid coercion issues, and defensively handling cases where the cmd parameter in runNotify might be undefined or null.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/core/src/lib/fs/process.ts
Comment thread packages/core/src/cli.ts
@agjs agjs merged commit e99737e into main Jun 23, 2026
8 checks passed
@agjs agjs deleted the fix/notify-unbounded-hang branch June 23, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant