Skip to content

fix: bound Stop hook stdin read so disabled gate cannot hang on Windows (#530) - #544

Open
stantheman0128 wants to merge 1 commit into
openai:mainfrom
stantheman0128:fix/530-stop-hook-eof-windows
Open

stantheman0128 wants to merge 1 commit into
openai:mainfrom
stantheman0128:fix/530-stop-hook-eof-windows

Conversation

@stantheman0128

Copy link
Copy Markdown

Summary

Fixes #530. stop-review-gate-hook.mjs used fs.readFileSync(0), which blocks until stdin EOF. On Windows, Claude Code can leave the write end open after sending the Stop payload, so the hook sat until the 900s hook timeout even when stopReviewGate was already false.

Changes:

  • Resolve config from CLAUDE_PROJECT_DIR / cwd first and take a short timed stdin read when the gate is off
  • Shared readHookInput(timeoutMs) helper for bounded data/end collection
  • When the gate is on, still read stdin with a 30s bound instead of an unbounded sync read

Related open EAGAIN PRs (#123/#132/#165) target #120 (non-blocking EAGAIN throws). This is a different failure mode: missing EOF with a disabled gate.

Evidence

node --test --test-name-pattern "stop hook logs running|stdin never|..." tests/runtime.test.mjs
pass: stop hook logs running tasks to stderr without blocking when the review gate is disabled
pass: stop hook with disabled gate exits quickly when stdin never receives EOF
pass: stop hook allows the stop when the review gate is enabled and the stop-time review task is clean
3 pass, 0 fail

AI assistance

Assisted by Cursor/Grok. Human author: Stan Shih (@stantheman0128). I reviewed the diff and ran the tests above.

fs.readFileSync(0) waits for EOF. On Windows Claude Code may leave the pipe open, so the Stop hook hit the 900s timeout even when stopReviewGate was false. Check config from cwd/env first and use a timed stdin reader (openai#530).

Co-authored-by: Cursor <[email protected]>
@stantheman0128
stantheman0128 requested a review from a team July 23, 2026 10:29

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bb448044d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +153 to +154
if (!preliminaryConfig.stopReviewGate) {
const input = await readHookInput(DISABLED_GATE_STDIN_TIMEOUT_MS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-check the gate for stdin.cwd before returning

When the hook is launched without CLAUDE_PROJECT_DIR or from a different working directory than the project in the hook payload, this early return trusts the config for cwdGuess. The subsequent short read may discover input.cwd, but the branch only logs running tasks and returns, so a project whose payload cwd has stopReviewGate: true can stop without running the review gate. This regresses the previous behavior, which loaded config after reading input.cwd; re-check the actual workspace's config before deciding the gate is disabled.

Useful? React with 👍 / 👎.

CBEPX added a commit to CBEPX/codex-plugin-cc that referenced this pull request Sep 28, 2026
…eview prompt via stdin

New lib/hook-input.mjs readHookInput(): streams stdin with a deadline (2 s
default, CODEX_HOOK_STDIN_TIMEOUT_MS for tests), a 1 MiB byte limit and a
StringDecoder across chunk boundaries. EOF parses the whole input; at the
deadline a buffer that is already a complete JSON object is accepted (only
the EOF was late), anything else is `timeout`; over the limit is `overflow`
without parsing; bad JSON is `invalid-json`. Listeners and the timer are
removed and stdin destroyed, so the hook exits right after deciding.

Stop hook: any read error blocks as before (fail-closed), except a timeout
with nothing received while the workspace gate (CLAUDE_PROJECT_DIR / cwd) is
off, which allows (openai#530/openai#544). With the gate on, a timeout blocks naming
"hook input did not arrive". The review prompt now reaches the companion via
`task --prompt-stdin`, not argv, lifting the Windows/Linux argv limits.

SessionStart/SessionEnd hook: reads with a 1 s deadline before the 12 s
SessionEnd budget starts; on any read error it prints one stderr line and
returns without cleanup. The hooks.json timeout test now asserts 15 s >
budget + read.

readStdinIfPiped (companion): readSync loop that keeps accumulated bytes
across EAGAIN on a non-blocking stdin (up to 50 x 20 ms consecutive) and
throws on exhaustion instead of returning a partial prompt
(openai#120/openai#247/openai#123/openai#150/openai#165).

Co-authored-by: stantheman0128 <[email protected]>
Co-authored-by: tmchow <[email protected]>
Co-Authored-By: Claude Fable 5.1 <[email protected]>
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.

Stop hook hangs until 900s timeout on Windows even when review gate is disabled (stdin EOF never arrives)

1 participant