Repository navigation
Description
Version
codebase-memory-mcp 0.9.1-rc.1
Platform
macOS (Apple Silicon)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
codebase-memory-mcp cli <tool> invoked with no flags blocks forever reading stdin whenever stdin is an open pipe that never delivers data. It produces no output and never exits.
This hits tools that legitimately need no arguments — list_projects is the obvious one. Passing any flag avoids it (index_status --project X and query_graph --project X --query "..." both complete normally against the same open-pipe stdin), so the trigger is specifically "no flags given → unconditionally read args from stdin".
That is a deadlock for automation. Any hook, CI step, git hook, or agent harness that runs cli list_projects while stdin is an inherited pipe hangs indefinitely rather than failing. I hit it by accident: a shell script doing
P=$(codebase-memory-mcp cli list_projects | grep -o '...' | head -1)hung, and the process was still stuck 14 minutes later. A sample(1) of the stuck process shows the main thread parked in fread on fd 0:
2602 Thread_13001004 DispatchQueue_1: com.apple.main-thread (serial)
+ 2602 start (in dyld) + 6992
+ 2602 ??? (in codebase-memory-mcp) + 0x5474
+ 2602 ??? (in codebase-memory-mcp) + 0x5f9c
+ 2602 ??? (in codebase-memory-mcp) + 0x8768
+ 2602 fread (in libsystem_c.dylib) + 136
+ 2602 __fread (in libsystem_c.dylib) + 488
+ 2602 __srefill1 (in libsystem_c.dylib) + 36
+ 2602 _sread (in libsystem_c.dylib) + 32
+ 2602 __sread (in libsystem_c.dylib) + 24
+ 2602 __read_nocancel (in libsystem_kernel.dylib) + 8
Expected: only read stdin when it is actually ready/has data (select/poll with a zero timeout, or check isatty/S_ISFIFO plus a non-blocking peek), or only when the tool actually requires arguments that weren't supplied. A tool that needs no arguments should never block on stdin. Failing that, a short read timeout with a clear error beats an unbounded hang.
I assume this arrives with the RC's new "args via piped stdin" input path (echo '<json>' | codebase-memory-mcp cli <tool>, per the deprecation notice on raw-JSON args).
Reproduction
No repo needed — any indexed project, or none at all.
# stdin is an open pipe with a writer that never writes
mkfifo /tmp/cbm-fifo
sleep 120 > /tmp/cbm-fifo &
# hangs forever, zero bytes of output:
codebase-memory-mcp cli list_projects < /tmp/cbm-fifo
# control — returns immediately, 896 bytes:
codebase-memory-mcp cli list_projects < /dev/null
# control — flags present, same open-pipe stdin, completes fine:
codebase-memory-mcp cli index_status --project <some-project> < /tmp/cbm-fifoMeasured with a 12-second watchdog (perl -e 'alarm 12; exec @ARGV'):
| invocation | stdin | result |
|---|---|---|
cli list_projects |
FIFO, held open, no data | hang, 0 bytes, killed by watchdog |
cli list_projects |
/dev/null |
exit 0, 896 bytes |
cli index_status --project cbm-rc-repros |
FIFO, held open, no data | exit 0, 31,970 bytes |
cli query_graph --project cbm-rc-repros --query "MATCH (n) RETURN n.name LIMIT 1" |
FIFO, held open, no data | exit 0, 50 bytes |
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Posted by Claude Code on behalf of @artaommahe
Activity
artaommahe commentedon Jul 30, 2026
My stated trigger is wrong and would send a fix in the wrong direction. Correcting it, and adding a realistic repro that replaces the contrived one.
Correction: "passing any flag avoids it" is false
--jsonand--progressare flags, and both still hang. Re-measured, FIFO stdin held open by a writer that never writes, 8-second watchdog:cli list_projectscli list_projects --jsoncli list_projects --progresscli index_status(required--projectomitted)cli list_projects '{}'(positional)cli get_graph_schema --project Xcli index_status --project Xcli list_projects/dev/nullSo it isn't "no flags". Observationally the read is attempted when the first argument-bearing token after the tool name is absent —
--json/--progressare consumed as CLI options before that check and don't count, and a positional{}or a--flag valuepair does.Two consequences I under-sold in the original report:
cli index_statuswith the required--projectomitted hangs, where the same command with< /dev/nullprints a perfectly goodmissing required argument: project. So the ordinary "I forgot a flag" path deadlocks instead of erroring — that's a much more common way to hit this than my FIFO scenario.list_projects's schema has zero properties ({"type":"object","properties":{}}intools/list). The process performs an unbounded read for a tool that cannot accept an argument at all.Also: interactive use is unaffected — under a real pty, output is produced immediately. So this only bites non-interactive callers, which is exactly where it's hardest to notice.
Replace my repro with this one — it isn't contrived
mkfifoinvites "who does that?". Node'schild_process.spawndefaults tostdio: ['pipe','pipe','pipe'], and the parent must explicitly callchild.stdin.end(). Almost nobody does that for a command they aren't writing to. Verified on this machine:That is the ordinary way a Node or Electron wrapper, VS Code extension, or MCP-adjacent tool shells out. My own shell script hit the same thing and sat stuck for 14 minutes before I noticed.
Narrowing my title claim
"hooks/CI" is too broad and I should correct it: GitHub Actions gives steps
/dev/null, and a hook that writes JSON and closes gets EOF, so neither hangs. The accurate scope is any wrapper that spawns the binary with default piped stdin — Node/Electron especially.Revised ask
My original "check
isatty" suggestion is likely redundant given interactive behaviour already differs. The substantive requests are:poll/selectwith a short deadline, or read only when data is already available — so a never-delivering pipe produces an error rather than an indefinite block.missing required argumenterror rather than blocking.Possibly a duplicate of a symptom already reported
#1195 ("
list_projectshangs indefinitely with 2 indexed projects, v0.9.0, macOS arm64, cli") looks like this bug diagnosed as a project-count problem — same tool, same platform, same CLI surface. Worth checking whether that reporter's invocation had piped stdin; if so, this is the root cause and that issue can be closed against it.Posted by Claude Code on behalf of @artaommahe
[-]cli hangs forever reading stdin when invoked with no flags and stdin is an open pipe (deadlocks hooks/CI/agent harnesses)[/-][+]cli hangs forever reading stdin when no argument-bearing token follows the tool name and stdin is an open pipe (default Node spawn deadlocks)[/+]DeusData commentedon Aug 3, 2026
Thank you for correcting the trigger and replacing the FIFO-only example with the default Node spawn case. Current CLI argument resolution treats any non-TTY stdin as piped JSON and fully slurps it before dispatch, so an open pipe with no data blocks indefinitely even for a zero-argument tool. This is distinct from #1195, whose hangs are state-dependent across cache enumeration and occur with completed stdin. I have removed the unrelated parsing/Windows labels and routed this at high priority in
0.9.1-rc.DeusData commentedon Aug 14, 2026
@artaommahe — thank you twice over: once for the
sample(1)trace parked infreadon fd 0, and once for coming back to correct your own trigger. The corrected table (with--jsonand--progressstill hanging, andindex_statusdeadlocking when its required flag is merely forgotten) is what makes this concrete, and replacing themkfiforepro with a default Nodespawnwas exactly right — that is how most callers will actually hit it.Confirmed in code: when stdin is not a TTY,
src/main.cslurps it to EOF as the argument source, so a pipe that nobody ever closes blocks forever. This is being fixed and is in scope for tonight's v0.10.5, which is aimed at the issues that make cbm impossible to operate.Sitting stuck for 14 minutes because the tool was waiting on a pipe that was never going to speak is a bad way to spend an afternoon. Sorry.
Merge pull request #1643 from DeusData/fix/no-stdin-slurp-without-flags
fix(cli): never read stdin for a tool that declares no arguments (Deu…
Merge pull request #1704 from DeusData/fix/stdin-gate-tests-post-1181
fix(test): the stdin gate's DeusData#1359 guards survive a registry w…
Fix CLI hangs on argument-free commands with open stdin
fix(cli): never wait on a stdin stream for a call complete without ar…