fix(worker): wrap Claude CLI in PTY to fix stdout buffering hang - #1678
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical bug where the Claude bridge would hang due to Node.js full stdout buffering when interacting with the Claude CLI via non-TTY pipes. By introducing a pseudo-terminal wrapper, the output is now line-buffered, ensuring proper streaming. Concurrently, this change enhances security by implementing necessary shell escaping for user-provided inputs, mitigating potential command injection risks within the worker. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors the execution of the claude command by wrapping it in script -qfc to allocate a PTY, addressing Node.js stdout buffering issues. It also introduces shell escaping for the prompt argument to prevent command injection, and a new test prompt_shell_escaping to verify this. The review identifies a potential command injection vulnerability with resume_session_id due to a lack of proper escaping and suggests adding test cases for resume_session_id escaping to improve robustness.
4a30bf2 to
ad67857
Compare
zmanian
left a comment
There was a problem hiding this comment.
Review: fix(worker): wrap Claude CLI in PTY to fix stdout buffering hang
PTY is the right fix for Node.js stdout buffering, but the implementation introduces security risk.
Critical
- Shell injection via unescaped
self.config.model: Interpolated directly into the shell command string. Must be escaped withshell_escape()likepromptandsession_idare.
High
- Security regression from
Command::arg()to shell string: Previous code passed args viaexecve(injection-safe by construction). New code goes throughscript -qfcwhich invokes/bin/sh -c. Consider using thepty-processcrate for PTY allocation while keepingCommand::arg().
Medium
-
script -qfcis GNU-only: macOSscripthas different syntax. Documented as Docker-only but a maintenance trap for local testing. -
stdout/stderr separation through PTY:
scriptcan merge streams. Verify NDJSON parsing still works when stderr lines from Claude CLI are potentially interleaved into stdout.
Positive
- Thorough escaping tests covering quotes, metacharacters, adversarial session IDs.
Fix the model escaping at minimum. Strongly consider the PTY crate approach to avoid the shell interpretation layer entirely.
ad67857 to
befb6f3
Compare
- Add pty-process crate (MIT, tokio async support) for PTY allocation - Spawn claude CLI with pty-process::Command::arg() chaining instead of building a shell string for script -qfc - Eliminates all shell injection surfaces: prompt, model, session_id are passed via execve, never interpreted by a shell - Keep stderr on separate pipe to prevent NDJSON parse breakage (pty-process attaches PTY to all fds by default) - Gate PTY behind #[cfg(unix)] with direct-spawn fallback for Windows CI - Read stdout from PTY master (implements tokio::io::AsyncRead) - Add regression tests: arg vector construction + PTY allocation Addresses review feedback from zmanian and gemini-code-assist. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
befb6f3 to
a3de012
Compare
zmanian
left a comment
There was a problem hiding this comment.
Re-review: shell injection via script -qfc is fully resolved -- replaced with pty-process crate using execve directly. No shell involved. Arguments passed via Command::arg(). Stderr kept separate from PTY. Excellent fix. Approve.
…PTY (nearai#1678) - Add pty-process crate (MIT, tokio async support) for PTY allocation - Spawn claude CLI with pty-process::Command::arg() chaining instead of building a shell string for script -qfc - Eliminates all shell injection surfaces: prompt, model, session_id are passed via execve, never interpreted by a shell - Keep stderr on separate pipe to prevent NDJSON parse breakage (pty-process attaches PTY to all fds by default) - Gate PTY behind #[cfg(unix)] with direct-spawn fallback for Windows CI - Read stdout from PTY master (implements tokio::io::AsyncRead) - Add regression tests: arg vector construction + PTY allocation Addresses review feedback from zmanian and gemini-code-assist. Co-authored-by: j-bloggs <j-bloggs@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…PTY (nearai#1678) - Add pty-process crate (MIT, tokio async support) for PTY allocation - Spawn claude CLI with pty-process::Command::arg() chaining instead of building a shell string for script -qfc - Eliminates all shell injection surfaces: prompt, model, session_id are passed via execve, never interpreted by a shell - Keep stderr on separate pipe to prevent NDJSON parse breakage (pty-process attaches PTY to all fds by default) - Gate PTY behind #[cfg(unix)] with direct-spawn fallback for Windows CI - Read stdout from PTY master (implements tokio::io::AsyncRead) - Add regression tests: arg vector construction + PTY allocation Addresses review feedback from zmanian and gemini-code-assist. Co-authored-by: j-bloggs <j-bloggs@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Change Type
Linked Issue
Addresses review feedback from zmanian and gemini-code-assist on this PR.
Validation
Security Impact
Database Impact
None
Blast Radius
Rollback Plan
Revert to staging Command::new("claude").arg() without PTY (re-introduces buffering hang but eliminates security risk).
Review Track
Track C - runtime changes in src/worker/, new dependency
Feature Parity
No FEATURE_PARITY.md changes needed.