fix(smolmachines): give pty_resize side-channel DEVNULL stdin so it survives under tmux
Inside tmux the dashboard's smolmachines launch crashed within
~100ms of the wrapper Popen-ing the main smolvm exec child —
sometimes with rc=137 (SIGKILL), sometimes with smolvm
spitting a runc-style "load `config.json`: cannot parse the
data: parse error: trailing garbage" and exiting 1. The same
wrapper ran fine outside tmux. Diagnostic logs showed the
SIGKILL landed ~100ms after the wrapper kicked off its
initial `sync()` (which fires the side-channel smolvm exec).
Root cause: the side-channel `subprocess.run([smolvm, machine,
exec, --, sh, -c, ...])` did not specify `stdin=`, so it
inherited the wrapper's stdin — the tmux pane PTY. The main
smolvm child (the agent session) also had that PTY as stdin.
Two concurrent smolvm processes sharing the PTY's
foreground-process-group / input plumbing caused smolvm to
abort one of them. iTerm's PTY plumbing apparently tolerated
this; tmux's didn't.
Fix is one line in `_push_size`: `stdin=subprocess.DEVNULL`.
The side-channel never needs stdin — it runs a fire-and-forget
`stty` and exits. Verified end-to-end: pre-fix the wrapper
crashed under `tmux respawn-pane` against a live VM; post-fix
the same invocation completes cleanly.
Also drop the diagnostic log added in 37bd11b — we have the
fix.
Regression test:
`test_side_channel_uses_devnull_stdin` locks the
`stdin=DEVNULL` invariant so a future "let's simplify the
subprocess.run kwargs" refactor surfaces this immediately.
637 unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,19 @@ class TestPushSize(unittest.TestCase):
|
||||
self.assertIn("rows 50", argv[8])
|
||||
self.assertIn("for f in /dev/pts/*", argv[8])
|
||||
|
||||
def test_side_channel_uses_devnull_stdin(self):
|
||||
# Load-bearing regression: under tmux, inheriting the
|
||||
# pane PTY as the side-channel's stdin makes smolvm crash
|
||||
# within ~100ms (concurrent smolvm processes sharing the
|
||||
# PTY's FG-PG / input plumbing). DEVNULL stdin sidesteps
|
||||
# the interaction.
|
||||
with patch.object(pty_resize.subprocess, "run") as run:
|
||||
pty_resize._push_size("claude-bottle-m", 24, 80)
|
||||
self.assertEqual(
|
||||
pty_resize.subprocess.DEVNULL,
|
||||
run.call_args.kwargs.get("stdin"),
|
||||
)
|
||||
|
||||
def test_swallows_subprocess_failures(self):
|
||||
# `check=False` + DEVNULL streams: a side-channel failure
|
||||
# mustn't break the operator's session.
|
||||
|
||||
Reference in New Issue
Block a user