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>