test: mock Claude CLI integration tests + dead export cleanup#8
Merged
Conversation
Previously the 53 tests in test/stop-hook.test.js exercised every
branch of hooks/stop-hook.js EXCEPT the one where it actually spawns
`claude -p --model haiku ...`. All tests deliberately took the
"no tool uses", "max iterations", "missing transcript", "recursion
guard", or "corrupt state" branches to avoid burning real Haiku
tokens. That left the single riskiest path — the Windows cmd.exe
shell-quoted subprocess invocation with complex JSON prompts —
completely uncovered.
Fix: add test/stop-hook-haiku.test.js which creates a temporary
directory with a cross-platform mock Claude CLI (a POSIX shell
wrapper `claude` + a Windows `claude.cmd` batch wrapper + a shared
Node.js implementation that reads WATCHDOG_FAKE_HAIKU_VERDICT from
env), prepends it to PATH, and runs the real hooks/stop-hook.js
subprocess with realistic HOOK_INPUT + state file + transcript.
New test cases exercise every verdict branch end-to-end through
the real spawnSync('claude', ...) code path:
1. FILE_CHANGES → hook emits {decision:"block"}, iteration bumped
2. NO_FILE_CHANGES → hook removes state file, exits silently
3. ambiguous (both markers present) → continue loop as safety
4. ambiguous (neither marker) → continue loop as safety
5. CLI failure (exit 1 from `claude -p`) → continue loop as safety
The mock correctly distinguishes `claude --version` (used by the
claudeCliAvailable() pre-flight check) from `claude -p` (the real
verdict call) so the CLI-failure branch is not confused with
CLI-missing.
Test count: 53 → 59. Suite runtime: ~800ms → ~1250ms. No external
dependencies added. Runs on the full CI matrix (Ubuntu/macOS/Windows
× Node 18/20/22).
Also clean up 4 dead exports flagged by a grep audit:
- lib/constants.js: STATE_DIR, STATE_FILE_PREFIX, STATE_FILE_SUFFIX
were used internally by stateFilePath() but exported to nobody.
Demoted to module-local const.
- lib/judge.js: claudeCliAvailable() is used internally by
askHaiku() but exported to nobody. Removed from module.exports.
Propagated the 53 → 59 count to all 7 READMEs (English + zh/ja/ko/
es/vi/pt) and updated the Testing section to mention the mock-CLI
integration coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the coverage gap around the real `spawnSync('claude', ...)` subprocess code path in `hooks/stop-hook.js`, and cleans up 4 dead exports flagged by a grep audit.
Why
Before this PR, the 53 tests in `test/stop-hook.test.js` exercised every branch of the stop hook except the one where it actually spawns `claude -p --model haiku ...`. Every test deliberately took the "no tool uses", "max iterations", "missing transcript", "recursion guard", or "corrupt state" branches to avoid burning real Haiku tokens in CI. That left the single riskiest path — the Windows `cmd.exe` shell-quoted subprocess invocation with complex JSON prompts — completely untested.
What
New test file: `test/stop-hook-haiku.test.js` (6 tests)
Creates a temporary bin directory with a cross-platform mock Claude CLI:
The test prepends this bin dir to `PATH` and runs the real `hooks/stop-hook.js` subprocess with realistic `HOOK_INPUT` + state file + transcript, then asserts on every verdict branch:
Plus a sanity test that the mock is actually resolvable on PATH.
Dead export cleanup
`lib/constants.js`: `STATE_DIR`, `STATE_FILE_PREFIX`, `STATE_FILE_SUFFIX` were used internally by `stateFilePath()` but exported to no one. Demoted to module-local `const`.
`lib/judge.js`: `claudeCliAvailable()` is used internally by `askHaiku()` but exported to no one. Removed from `module.exports`.
Doc updates
Test plan
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com