3fb305f654
`smolvm 0.8.0 machine exec -t` allocates an in-VM PTY but never forwards the host terminal's window size — the PTY starts at `0 0` and host resizes (tmux pane resize, terminal window resize) go unnoticed, so the claude TUI inside a smolmachines bottle renders for whatever tiny box it last saw and ignores operator resizes. `docker exec -it` propagates window-size changes automatically; smolvm doesn't. Workaround: a small Python wrapper (`backend/smolmachines/pty_resize.py`) that interposes between the operator's terminal and `smolvm machine exec`. It spawns smolvm as a child, traps host SIGWINCH, and on every resize (plus once at startup) runs a side-channel `smolvm machine exec --name <M> -- sh -c 'for f in /dev/pts/*; do stty -F $f cols X rows Y; done'`. The kernel delivers SIGWINCH to the in-VM foreground process group when the slave PTY's size changes, so claude picks up the new dimensions without extra signalling. `SmolmachinesBottle.claude_argv` prepends `[sys.executable, -m, claude_bottle.backend.smolmachines. pty_resize, <machine>, --, ...]` to the existing smolvm argv in TTY mode. Non-TTY mode (provisioning shell-outs) skips the wrapper — no PTY to resize. The wrapper survives the dashboard's `_build_resume_argv_with_fallback` shell-wrap because the split-at-`claude` token still finds the right position — the wrapper's prefix wraps the entire smolvm-exec framing. Tests: - `test_smolmachines_pty_resize.py` (new): argv parsing, the side-channel command shape (cols/rows / for-loop over /dev/pts/*), and `_read_winsize`'s fallback across stdin/stdout/stderr including the smolvm-allocated-PTY- reports-`0 0` ironic case. - `test_smolmachines_bottle.py`: updated TTY-mode assertions to unwrap the pty_resize prefix; added `TestClaudeArgvNoTTY` to lock the non-TTY skip. 636 unit tests pass. Removable when smolvm grows native SIGWINCH forwarding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>