Repository navigation
Fix EAGAIN crash when stdin is a non-blocking handle with no data - #16
Conversation
readStdinIfPiped called fs.readFileSync(0) whenever stdin was not a TTY. Under Claude Code's Bash tool (used by the devin-rescue subagent), fd 0 is a non-blocking handle with no data, so the read threw EAGAIN and the task command failed before Devin ran. Now fd 0 is read only when fstat says it is a FIFO or regular file, and EAGAIN/ENXIO are treated as empty input. Dependencies are injectable for unit tests, and an end-to-end test reads from a real non-blocking FIFO with an open, silent writer (tests/fs.test.mjs).
TheOnlyFusionCube
left a comment
There was a problem hiding this comment.
The EAGAIN guard addresses the original empty nonblocking-stdin crash, and preserving other I/O errors is good. Blocking issue: fs.readFileSync(0, 'utf8') may consume some FIFO bytes and then throw EAGAIN while the writer remains open. Catching EAGAIN around the entire read loses those bytes and returns an empty prompt. The added FIFO regression only checks an empty FIFO. Please use an incremental readSync loop that preserves successfully read chunks, ends on EOF or EAGAIN, and bounds input appropriately. Add a real FIFO test with bytes already written while the writer stays open, asserting the exact input is returned, plus a multi-chunk test. I ran a focused reproduction of this patch's reader on Node 22: the empty FIFO case passed, but a FIFO containing PROMPT DATA\n returned "" instead of the payload. The existing Node 18/20/22 CI Test matrix passed; this is a missing behavioral case, not a CI infrastructure failure. The HOL Scanner reports the same one high as main and is a separate issue.
| throw e; | ||
| } | ||
| try { | ||
| return readImpl(0, "utf8"); |
There was a problem hiding this comment.
Blocking: readFileSync may read partial FIFO data and then throw EAGAIN; the catch discards the already-read bytes. Verified with a real FIFO containing PROMPT DATA\n and an open writer: the function returned empty string. Replace with chunked reads that retain bytes up to EAGAIN/EOF and add a non-empty FIFO regression.
Read stdin with an incremental readSync loop so bytes already received from a non-blocking FIFO survive EAGAIN, decode once at the end, and throw if input exceeds an 8 MiB cap instead of truncating. Add real FIFO tests for pending data, multi-byte UTF-8 and multiple chunks.
|
Thanks, that was a real bug. Tests: new real mkfifo tests with an O_NONBLOCK reader and an open writer, covering |
TheOnlyFusionCube
left a comment
There was a problem hiding this comment.
Re-review: the incremental readSync loop preserves partial FIFO bytes across EAGAIN/ENXIO, decodes UTF-8 once after concatenation, enforces an explicit 8 MiB cap with an overflow probe, and retains propagation of other I/O errors. Real nonblocking FIFO tests now cover empty, populated, UTF-8, and multichunk streams; CI Test passed on the new head. The HOL scanner failure is also present on main and is tracked separately; do not relax the check. Approved.
83653b1
into
TheOnlyFusionCube:main
Re-file of the former #5, as requested in #4, rebased onto current main.
What was wrong:
readStdinIfPiped()calledfs.readFileSync(0)whenever stdin wasn't a TTY. Under Claude Code's Bash tool, which thedevin-rescuesubagent uses, fd 0 is a non-blocking handle with no data. The read threwEAGAINandtaskfailed before Devin ran, so/devin:rescuedidn't work from Claude Code.What changed
fstatreports a FIFO or a regular file.EAGAINandENXIOare treated as empty input. Other errors are rethrown.fstat, read andisTTYare injectable for tests.How tested
O_NONBLOCKand an open writer that sends nothing.readStdinIfPipedin a child process with that FIFO as stdin and asserts the result is"".EAGAIN: resource temporarily unavailableon current main, and passes with this change.mkfifo.node --test: 78/78.