refactor: Bottle.exec takes a user= kwarg, default node
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 41s

Promote the user-switch from a hardcoded `node` to a keyword arg
so callers can opt into root (or any other user) when needed.
Default stays `node` — matches the docker image's USER and the
smolmachines runuser default.

Lifts the change through the base ABC, docker, and smolmachines
backends:
- Base: `def exec(self, script, *, user="node")`.
- Docker: adds `-u <user>` to `docker exec` (no-op when user is
  node, the image's default).
- Smolmachines: `runuser -l <user> -c <script>` — `runuser -l
  root` is the trivial no-op form when the caller asked for root.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 15:00:13 -04:00
parent e26d459a97
commit af65c10361
3 changed files with 32 additions and 15 deletions
+14 -6
View File
@@ -119,12 +119,20 @@ class Bottle(ABC):
def exec_claude(self, argv: list[str], *, tty: bool = True) -> int: ...
@abstractmethod
def exec(self, script: str) -> ExecResult:
"""Run `script` as a POSIX shell script inside the bottle and
return the captured stdout/stderr/returncode. The bottle's
environment (including HTTPS_PROXY pointing at the pipelock
sidecar) is inherited by the child. Non-zero exit does not
raise — callers inspect `returncode` themselves."""
def exec(self, script: str, *, user: str = "node") -> ExecResult:
"""Run `script` as a POSIX shell script inside the bottle as
`user` (default `node`, matching the agent image's USER
directive) and return the captured stdout/stderr/returncode.
The bottle's environment (including HTTPS_PROXY pointing at
the pipelock sidecar) is inherited by the child. Non-zero
exit does not raise — callers inspect `returncode`
themselves.
Pass `user="root"` for shell-outs that need privileged file
writes / package install — provisioning calls that need root
bypass `Bottle.exec` and use the backend-specific raw
machine-exec helper, but the tests have a legitimate use
case for arbitrary-user runs."""
@abstractmethod
def cp_in(self, host_path: str, container_path: str) -> None: ...