Skip to content

Bound sandbox-gh-wrapper.cjs's stdin read so it can't hang forever - #6

Merged
nananek merged 1 commit into
masterfrom
gh-wrapper-stdin-timeout
Jul 28, 2026
Merged

Bound sandbox-gh-wrapper.cjs's stdin read so it can't hang forever#6
nananek merged 1 commit into
masterfrom
gh-wrapper-stdin-timeout

Conversation

@nananek

@nananek nananek commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Reported from real use in another project's sandboxed session: gh pr create --base develop --title "..." --body "$(cat <<'BODY' ...multi-line markdown... BODY)" hung inside a ccserver sandbox. Switching to --body-file <path> worked around it.
  • Investigated sandbox-gh-wrapper.cjs's readStdin() (added in Scope gh CLI through the git broker instead of disabling it #5) and found a real, independent bug: it has no timeout at all and relies entirely on process.stdin.isTTY to decide whether to skip reading. That's not a hard guarantee -- whatever invokes gh inside the sandbox may hand this process a non-TTY stdin (e.g. an fd shared with a longer-lived shell/pty) that was never going to send EOF on its own, and the vast majority of gh invocations don't read stdin at all (title/body/etc. come in as flags).
  • Note: this may or may not be the exact root cause of the reported hang (switching to --body-file should still go through the same unconditional readStdin() call, which is a bit inconsistent with that being the fix that worked -- the original hang could equally have been a Bash-tool/pty issue with typing a large heredoc, unrelated to this code). Fixing this regardless since it's a real latent hang risk in code I own here.

Fix

readStdin() now:

  • gives up after 200ms if nothing arrives at all (the common case: no stdin intended), instead of blocking forever
  • once real data starts flowing, allows a generous-but-bounded 2s inactivity window to finish, rather than waiting indefinitely for the stream to close

Test plan

  • npm test -- 66 tests passing (63 existing + 3 new)
  • New server/ws/sandbox-gh-wrapper.test.js: spawns the real wrapper against a real broker + fake gh, with stdin left open and never written to -- completes in ~300ms instead of hanging (this is the exact regression scenario)
  • Same test also covers stdin closed immediately with no data, and real piped content (--body-file -) still being forwarded correctly -- the timeout doesn't break legitimate piped input

🤖 Generated with Claude Code

https://claude.ai/code/session_01YNFA5BLgmig7RswZypScjX

Reported from real use: `gh pr create ... --body "$(cat <<'BODY' ... BODY)"`
run inside a ccserver sandbox hung. readStdin() had no timeout at all and
relied entirely on process.stdin.isTTY to decide whether to skip it -- but
that's not a hard guarantee: whatever invokes `gh` inside the sandbox may
hand this process a non-TTY stdin (e.g. an fd shared with a longer-lived
shell/pty) that was never going to send EOF on its own, and the vast
majority of gh invocations don't read stdin at all (input comes via flags).

readStdin() now gives up after 200ms if nothing arrives at all, and once
real data starts flowing, allows a generous-but-bounded 2s inactivity
window rather than waiting indefinitely for the stream to close.

Added server/ws/sandbox-gh-wrapper.test.js as a regression test: spawns
the wrapper against a real broker + fake gh with stdin left open and
never written to, and asserts it still completes in well under a second.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNFA5BLgmig7RswZypScjX
@nananek
nananek merged commit a4ba396 into master Jul 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant