fix(smolmachines): run claude mcp add as node so config lands in node's home
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 40s

provision_supervise dispatched `claude mcp add --scope user`
through `smolvm machine_exec`, which runs as root by default.
The MCP entry got written to root's ~/.claude.json — but the
agent's claude reads /home/node/.claude.json, so `/mcp` showed
"No MCP servers configured" inside the bottle.

Wrap the exec in `runuser -u node -- env HOME=/home/node ...`
so the config writes to the right home. Same pattern as the
interactive exec_claude / Bottle.exec wrappers — `smolvm
machine_exec` is always root, so any command that touches user
state has to switch UID + set HOME explicitly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 16:08:08 -04:00
parent 515306cd4a
commit d02fe50193
2 changed files with 14 additions and 3 deletions
+7 -3
View File
@@ -487,11 +487,15 @@ class TestProvisionSupervise(unittest.TestCase):
_supervise.provision_supervise(plan, "claude-bottle-demo-abc12")
ex.assert_called_once()
argv = ex.call_args.args[1]
# claude mcp add --scope user --transport http supervise <url>
# — URL is the agent-side endpoint (host loopback +
# discovered port), not the docker bridge IP.
# `claude mcp add --scope user` writes to ~/.claude.json,
# and the agent is the `node` user — switch UID + set
# HOME so the config lands in /home/node/.claude.json,
# not root's. URL is the agent-side endpoint (host
# loopback + discovered port), not the docker bridge IP.
self.assertEqual(
[
"runuser", "-u", "node", "--",
"env", "HOME=/home/node",
"claude", "mcp", "add",
"--scope", "user",
"--transport", "http",