-
Notifications
You must be signed in to change notification settings - Fork 118
Description
Bug Description
The SessionStart and Stop hooks cause Claude Code to hang for the full 30-second hook timeout on Windows because readStdin() in src/lib/stdin.js has no timeout. It waits indefinitely for process.stdin to emit an end event, which never fires on Windows when Claude Code spawns the hook subprocess.
Root Cause
The original readStdin() implementation:
async function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => { data += chunk; });
process.stdin.on('end', () => {
try {
resolve(data.trim() ? JSON.parse(data) : {});
} catch (err) {
reject(new Error(`Failed to parse stdin JSON: ${err.message}`));
}
});
process.stdin.on('error', reject);
if (process.stdin.isTTY) resolve({});
});
}On Windows (Git Bash / MSYS2), the stdin end event is never emitted for hook subprocesses. The isTTY check doesn't help because stdin is piped (not a TTY), so the promise never resolves. The script hangs at the very first line of execution — before authentication or API calls even begin — and consumes the entire 30-second hook timeout.
Suggested Fix
Add a configurable timeout to readStdin() so it resolves with whatever data has been received (or an empty object) after a reasonable wait:
async function readStdin(timeoutMs = 3000) {
return new Promise((resolve, reject) => {
let data = '';
let resolved = false;
function finish(value) {
if (resolved) return;
resolved = true;
clearTimeout(timer);
resolve(value);
}
const timer = setTimeout(() => {
try {
finish(data.trim() ? JSON.parse(data) : {});
} catch (err) {
finish({});
}
}, timeoutMs);
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => { data += chunk; });
process.stdin.on('end', () => {
try {
finish(data.trim() ? JSON.parse(data) : {});
} catch (err) {
if (!resolved) {
resolved = true;
clearTimeout(timer);
reject(new Error(`Failed to parse stdin JSON: ${err.message}`));
}
}
});
process.stdin.on('error', (err) => {
if (!resolved) {
resolved = true;
clearTimeout(timer);
reject(err);
}
});
if (process.stdin.isTTY) finish({});
});
}Secondary Issue: ${CLAUDE_PLUGIN_ROOT} not resolved on Windows
The hook commands in hooks.json use ${CLAUDE_PLUGIN_ROOT}:
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.cjs\""On Windows with Git Bash / MSYS2, ${CLAUDE_PLUGIN_ROOT} is not expanded by the shell when Claude Code executes the hook command. This appears to be a Claude Code issue — the variable is either not set in the hook's execution environment, or the command string is not processed through shell expansion before execution.
This is likely a Claude Code platform bug (not specific to this plugin) that affects any plugin using ${CLAUDE_PLUGIN_ROOT} in hook commands on Windows. It may need to be reported to Anthropic separately.
As a workaround, we replaced the hook commands with hardcoded absolute paths:
"command": "volta run node ~/.claude/plugins/marketplaces/supermemory-plugins/plugin/scripts/context-hook.cjs"Environment
- OS: Windows 11 (Git Bash / MSYS2 shell)
- Node runner: Volta
- Plugin version: 0.0.1 (commit e323919)
- Claude Code: Latest
Steps to Reproduce
- Install the plugin on Windows
- Restart Claude Code
- Observe ~30 second hang on every startup
- Debug logs show the hook timing out before any authentication or API activity