Debugging a live codex smolmachines bottle surfaced three independent
failures past the sign-in screen; fix each so forward_host_credentials
works end to end:
- codex_auth: dummy access/id tokens now inherit the *real* host token's
exp instead of now+1h. Codex (0.135) refreshes when its local token's
JWT exp lapses; with a placeholder refresh_token that refresh fails and
drops to the sign-in screen. Aligning exp tracks the real token's life.
- prepare: set CODEX_CA_CERTIFICATE to the agent CA bundle for codex
bottles. Codex is rustls and ignores the system store / NODE_EXTRA_CA_
CERTS; it reads CODEX_CA_CERTIFICATE (fallback SSL_CERT_FILE) for custom
roots across HTTPS + wss, so it must be pointed at the egress MITM CA or
injection can't work without tls_passthrough.
- pipelock: auto tls_passthrough the Codex API hosts when
forward_host_credentials is on. Egress injects the bearer before
pipelock, whose header DLP then flags the JWT ("request header contains
secret") and the retry storm trips its 429. passthrough host-gates the
CONNECT but skips decrypt+rescan of egress-owned auth. The auto-added
routes aren't in bottle.egress.routes, so the hosts are added explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard's launch path crashed inside tmux but worked
outside it. Root cause: `python -m
claude_bottle.backend.smolmachines.pty_resize` needs the
`claude_bottle` package on `sys.path`, which by default comes
from cwd. The outside-tmux path is `subprocess.run(...)` —
inherits the dashboard process's cwd (the repo root, where
`claude_bottle/` lives), so the import resolves. The
inside-tmux path is `tmux split-window / respawn-pane <argv>`,
and tmux opens the new pane with the pane's OWN cwd, not the
cwd of the process invoking split-window. If the operator
started their tmux pane anywhere outside the repo (typical:
`$HOME`), the wrapper hit `ModuleNotFoundError: No module
named 'claude_bottle'` and tmux closed the pane immediately.
Sidestep the cwd dependence by invoking the wrapper as
`python <absolute-path-to-pty_resize.py>` instead of
`python -m <dotted-path>`. The wrapper has no
`claude_bottle.*` imports — it's stdlib-only — so it runs as
a standalone script anywhere on the filesystem. The absolute
path comes from `pty_resize.__file__` at module-load time.
Tests:
- `test_pty_resize_wrapper_prefix`: updated to assert the
absolute-script-path shape rather than the `-m <dotted>`
shape.
- `test_no_wrapper_when_tty_false`: the substring check now
uses `any("pty_resize" in a for a in argv)` instead of
string-joining (so the absolute path's "pty_resize.py"
filename match still catches a regression).
636 unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`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>
Launching a smolmachines agent from the dashboard inside tmux
crashed with
AttributeError: 'SmolmachinesBottle' object has no attribute
'claude_docker_argv'
because the tmux pane-respawn path called
`bottle.claude_docker_argv(...)` directly — a method that only
existed on DockerBottle. The foreground-handoff path (curses
endwin → subprocess.run → restore) doesn't hit it; it goes
through `bottle.exec_claude` which is on the ABC.
- Move the argv builder onto the `Bottle` ABC as
`claude_argv(argv, *, tty=True) -> list[str]`. Both backends
implement it; both `exec_claude` impls collapse to
`subprocess.run(self.claude_argv(argv, tty=tty), check=False)`.
- DockerBottle: rename `claude_docker_argv` → `claude_argv`,
body unchanged.
- SmolmachinesBottle: extract the argv-building from
`exec_claude` into `claude_argv`; the new method returns the
full `smolvm machine exec --name … -- runuser -u node --
claude …` argv. The `runuser` switch lives on the
exec-framing prefix so the dashboard's
`_build_resume_argv_with_fallback` split-at-"claude" trick
keeps the UID switch when wrapping the claude tail in
`sh -c "… --continue || …"`.
- Dashboard: drop the docker-specific wording — local + helper
arg names `docker_argv` → `claude_argv`; docstrings on
`_build_resume_argv_with_fallback`, `_build_split_pane_argv`,
`_build_respawn_pane_argv` now say "backend-exec argv". The
shell-fallback wrap is unchanged; the existing logic works
for smolmachines because `claude` is still the marker token.
Tests:
- `tests/unit/test_smolmachines_bottle.py` (new): locks down
the smolmachines argv shape — prompt-file flag injection,
guest-env `-e K=V` forwarding, TTY toggle, runuser-precedes-
claude invariant.
- `test_docker_bottle.py`: TestClaudeDockerArgv →
TestClaudeArgv; method renames follow.
- `test_dashboard_active_agents.py`: docstring follow.
615 unit tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>