fix(smolmachines): invoke pty_resize by absolute path, not python -m
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>
This commit is contained in:
@@ -22,9 +22,21 @@ import sys
|
||||
from typing import Mapping
|
||||
|
||||
from .. import Bottle, ExecResult
|
||||
from . import pty_resize as _pty_resize
|
||||
from . import smolvm as _smolvm
|
||||
|
||||
|
||||
# Absolute path to the pty_resize wrapper. The dashboard's tmux
|
||||
# pane (split-window / respawn-pane) opens the new pane in its
|
||||
# OWN cwd, not the cwd of the process running split-window — so
|
||||
# invoking the wrapper as `python -m <dotted-path>` would fail
|
||||
# with ModuleNotFoundError whenever the operator's tmux pane was
|
||||
# started from anywhere outside the claude-bottle repo. Absolute
|
||||
# path sidesteps the cwd dependence (the wrapper has no
|
||||
# claude_bottle.* imports, so it runs as a standalone script).
|
||||
_PTY_RESIZE_SCRIPT = _pty_resize.__file__
|
||||
|
||||
|
||||
# Per-user env the agent image's USER (node) expects. claude
|
||||
# reads ~/.claude.json + writes session state under ~/.claude/;
|
||||
# bare `runuser -u` inherits root's HOME=/root, which claude
|
||||
@@ -96,8 +108,7 @@ class SmolmachinesBottle(Bottle):
|
||||
# happen to go through this method) stay light.
|
||||
return flags
|
||||
return [
|
||||
sys.executable, "-m",
|
||||
"claude_bottle.backend.smolmachines.pty_resize",
|
||||
sys.executable, _PTY_RESIZE_SCRIPT,
|
||||
self.name, "--", *flags,
|
||||
]
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from claude_bottle.backend.smolmachines import pty_resize as _pty_resize
|
||||
from claude_bottle.backend.smolmachines.bottle import SmolmachinesBottle
|
||||
|
||||
|
||||
@@ -40,13 +41,15 @@ class TestClaudeArgvWrapped(unittest.TestCase):
|
||||
|
||||
def test_pty_resize_wrapper_prefix(self):
|
||||
argv = _bottle().claude_argv([])
|
||||
# Absolute script path (not `-m <dotted>`) so the tmux
|
||||
# pane's cwd doesn't matter — see the `_PTY_RESIZE_SCRIPT`
|
||||
# docstring in bottle.py.
|
||||
self.assertEqual(
|
||||
[
|
||||
sys.executable, "-m",
|
||||
"claude_bottle.backend.smolmachines.pty_resize",
|
||||
sys.executable, _pty_resize.__file__,
|
||||
"claude-bottle-dev-abc", "--",
|
||||
],
|
||||
argv[:5],
|
||||
argv[:4],
|
||||
)
|
||||
|
||||
def test_minimal_inner_argv_no_prompt(self):
|
||||
@@ -128,7 +131,7 @@ class TestClaudeArgvNoTTY(unittest.TestCase):
|
||||
def test_no_wrapper_when_tty_false(self):
|
||||
argv = _bottle().claude_argv([], tty=False)
|
||||
self.assertEqual("smolvm", argv[0])
|
||||
self.assertNotIn("pty_resize", " ".join(argv))
|
||||
self.assertFalse(any("pty_resize" in a for a in argv))
|
||||
|
||||
def test_tty_false_drops_it_flags(self):
|
||||
argv = _bottle().claude_argv([], tty=False)
|
||||
|
||||
Reference in New Issue
Block a user