fix(codex): trust bottle workspace on launch
test / unit (pull_request) Successful in 27s
test / integration (pull_request) Successful in 44s

This commit is contained in:
2026-06-01 17:13:48 -04:00
parent 3060dc553c
commit 3942b51184
4 changed files with 162 additions and 17 deletions
@@ -3,29 +3,72 @@
from __future__ import annotations
import os
import shlex
import subprocess
from ..bottle_plan import DockerBottlePlan
def provision_provider_auth(plan: DockerBottlePlan, target: str) -> None:
"""Copy a dummy Codex auth marker when host credentials are
forwarded through egress.
_CODEX_WORKSPACE = "/home/node/workspace"
The file contains no real access or refresh token values; it only
def provision_provider_auth(plan: DockerBottlePlan, target: str) -> None:
"""Prepare Codex home state inside a Docker bottle.
Every Codex bottle gets a minimal config.toml that trusts the
in-container workspace path. When host credentials are forwarded,
auth.json contains no real access or refresh token values; it only
nudges Codex into the same user/device auth branch as the host.
"""
if not plan.codex_auth_file:
if plan.agent_provider_template != "codex":
return
container_home = os.environ.get("BOT_BOTTLE_CONTAINER_HOME", "/home/node")
auth_dir = f"{container_home}/.codex"
auth_path = f"{auth_dir}/auth.json"
subprocess.run(
["docker", "exec", "-u", "0", target, "mkdir", "-p", auth_dir],
stdout=subprocess.DEVNULL,
check=True,
)
subprocess.run(
["docker", "exec", "-u", "0", target, "chown", "node:node", auth_dir],
stdout=subprocess.DEVNULL,
check=True,
)
subprocess.run(
["docker", "exec", "-u", "0", target, "chmod", "700", auth_dir],
stdout=subprocess.DEVNULL,
check=True,
)
config_path = f"{auth_dir}/config.toml"
config = (
f'[projects."{_CODEX_WORKSPACE}"]\n'
'trust_level = "trusted"\n'
)
subprocess.run(
[
"docker", "exec", "-u", "0", target,
"sh", "-c",
f"printf %s {shlex.quote(config)} > {shlex.quote(config_path)}",
],
stdout=subprocess.DEVNULL,
check=True,
)
subprocess.run(
["docker", "exec", "-u", "0", target, "chown", "node:node", config_path],
stdout=subprocess.DEVNULL,
check=True,
)
subprocess.run(
["docker", "exec", "-u", "0", target, "chmod", "600", config_path],
stdout=subprocess.DEVNULL,
check=True,
)
if not plan.codex_auth_file:
return
auth_path = f"{auth_dir}/auth.json"
subprocess.run(
["docker", "cp", str(plan.codex_auth_file), f"{target}:{auth_path}"],
stdout=subprocess.DEVNULL,
@@ -3,6 +3,7 @@
from __future__ import annotations
import os
import shlex
from ....log import die
from .. import smolvm as _smolvm
@@ -10,16 +11,18 @@ from ..bottle_plan import SmolmachinesBottlePlan
_DEFAULT_GUEST_HOME = "/home/node"
_CODEX_WORKSPACE = "/home/node/workspace"
def provision_provider_auth(plan: SmolmachinesBottlePlan, target: str) -> None:
"""Copy a dummy Codex auth marker when host credentials are
forwarded through egress.
"""Prepare Codex home state inside the smolmachine.
The real host access token remains in the egress bundle env; this
file only selects Codex's user/device auth code path.
Every Codex bottle gets a minimal config.toml that trusts the
in-guest workspace path. When host credentials are forwarded, the
real host access token remains in the egress bundle env; auth.json
only selects Codex's user/device auth code path.
"""
if not plan.codex_auth_file:
if plan.agent_provider_template != "codex":
return
guest_home = os.environ.get("BOT_BOTTLE_GUEST_HOME", _DEFAULT_GUEST_HOME)
auth_dir = plan.guest_env.get("CODEX_HOME", f"{guest_home}/.codex")
@@ -65,6 +68,29 @@ def provision_provider_auth(plan: SmolmachinesBottlePlan, target: str) -> None:
detail = f": {detail}"
die(f"codex host credentials: could not reset runtime db files{detail}")
config_path = f"{auth_dir}/config.toml"
config = (
f'[projects."{_CODEX_WORKSPACE}"]\n'
'trust_level = "trusted"\n'
)
result = _smolvm.machine_exec(
target,
[
"sh", "-c",
f"printf %s {shlex.quote(config)} > {shlex.quote(config_path)}",
],
)
if result.returncode != 0:
detail = (result.stderr or result.stdout).strip()
if detail:
detail = f": {detail}"
die(f"codex host credentials: could not write {config_path}{detail}")
_smolvm.machine_exec(target, ["chown", "node:node", config_path])
_smolvm.machine_exec(target, ["chmod", "600", config_path])
if not plan.codex_auth_file:
return
auth_path = f"{auth_dir}/auth.json"
_smolvm.machine_cp(str(plan.codex_auth_file), f"{target}:{auth_path}")
_smolvm.machine_exec(target, ["chown", "node:node", auth_path])
@@ -15,7 +15,11 @@ from bot_bottle.manifest import Manifest
from bot_bottle.pipelock import PipelockProxyPlan
def _plan(*, codex_auth_file: Path | None = None) -> DockerBottlePlan:
def _plan(
*,
codex_auth_file: Path | None = None,
agent_provider_template: str = "codex",
) -> DockerBottlePlan:
manifest = Manifest.from_json_obj({
"bottles": {"dev": {"agent_provider": {"template": "codex"}}},
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
@@ -58,18 +62,46 @@ def _plan(*, codex_auth_file: Path | None = None) -> DockerBottlePlan:
supervise_plan=None,
use_runsc=False,
agent_command="codex",
agent_provider_template="codex",
agent_provider_template=agent_provider_template,
codex_auth_file=codex_auth_file,
)
class TestProvisionProviderAuth(unittest.TestCase):
def test_noop_without_codex_auth_file(self):
def test_noop_for_non_codex_provider(self):
with patch.object(_provider_auth.subprocess, "run") as run:
_provider_auth.provision_provider_auth(
_plan(agent_provider_template="claude"), "bot-bottle-demo-abc12",
)
self.assertEqual(0, run.call_count)
def test_codex_provider_trusts_workspace_without_auth_file(self):
with patch.object(_provider_auth.subprocess, "run") as run:
_provider_auth.provision_provider_auth(
_plan(), "bot-bottle-demo-abc12",
)
self.assertEqual(0, run.call_count)
argvs = [call.args[0] for call in run.call_args_list]
self.assertIn(
["docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
"mkdir", "-p", "/home/node/.codex"],
argvs,
)
trust_config = next(
a for a in argvs
if a[:6] == ["docker", "exec", "-u", "0", "bot-bottle-demo-abc12", "sh"]
)
self.assertIn('[projects."/home/node/workspace"]', trust_config[-1])
self.assertIn('trust_level = "trusted"', trust_config[-1])
self.assertIn(
["docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
"chown", "node:node", "/home/node/.codex/config.toml"],
argvs,
)
self.assertIn(
["docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
"chmod", "600", "/home/node/.codex/config.toml"],
argvs,
)
def test_copies_dummy_auth_json_to_codex_home(self):
with patch.object(_provider_auth.subprocess, "run") as run:
@@ -83,6 +115,16 @@ class TestProvisionProviderAuth(unittest.TestCase):
"mkdir", "-p", "/home/node/.codex"],
argvs,
)
self.assertIn(
["docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
"chown", "node:node", "/home/node/.codex"],
argvs,
)
self.assertIn(
["docker", "exec", "-u", "0", "bot-bottle-demo-abc12",
"chmod", "700", "/home/node/.codex"],
argvs,
)
self.assertIn(
["docker", "cp", "/tmp/codex-auth.json",
"bot-bottle-demo-abc12:/home/node/.codex/auth.json"],
+36 -2
View File
@@ -57,6 +57,7 @@ def _plan(
agent_git_gate_host: str = "127.0.0.1:55555",
agent_supervise_url: str = "http://127.0.0.1:55556/",
codex_auth_file: Path | None = None,
agent_provider_template: str = "claude",
guest_env: dict[str, str] | None = None,
) -> SmolmachinesBottlePlan:
bottle_json: dict = {}
@@ -133,6 +134,7 @@ def _plan(
agent_git_gate_host=agent_git_gate_host,
agent_supervise_url=agent_supervise_url,
codex_auth_file=codex_auth_file,
agent_provider_template=agent_provider_template,
)
@@ -204,19 +206,45 @@ class TestProvisionProviderAuth(unittest.TestCase):
),
)
def test_noop_without_codex_auth_file(self):
def test_noop_for_non_codex_provider(self):
cp_p, ex_p = self._patch()
with cp_p as cp, ex_p as ex:
_provider_auth.provision_provider_auth(_plan(), "bot-bottle-demo-abc12")
self.assertEqual(0, cp.call_count)
self.assertEqual(0, ex.call_count)
def test_codex_provider_trusts_workspace_without_auth_file(self):
cp_p, ex_p = self._patch()
with cp_p as cp, ex_p as ex:
ex.return_value = SmolvmRunResult(0, "", "")
_provider_auth.provision_provider_auth(
_plan(agent_provider_template="codex"),
"bot-bottle-demo-abc12",
)
self.assertEqual(0, cp.call_count)
argv_seen = [call.args[1] for call in ex.call_args_list]
self.assertIn(["mkdir", "-p", "/home/node/.codex"], argv_seen)
trust_config = next(
a for a in argv_seen
if a[:2] == ["sh", "-c"] and "config.toml" in a[2]
)
self.assertIn('[projects."/home/node/workspace"]', trust_config[2])
self.assertIn('trust_level = "trusted"', trust_config[2])
self.assertIn(
["chown", "node:node", "/home/node/.codex/config.toml"],
argv_seen,
)
self.assertIn(["chmod", "600", "/home/node/.codex/config.toml"], argv_seen)
def test_copies_dummy_auth_json_to_codex_home(self):
cp_p, ex_p = self._patch()
with cp_p as cp, ex_p as ex:
ex.return_value = SmolvmRunResult(0, "Logged in using ChatGPT\n", "")
_provider_auth.provision_provider_auth(
_plan(codex_auth_file=Path("/tmp/codex-auth.json")),
_plan(
agent_provider_template="codex",
codex_auth_file=Path("/tmp/codex-auth.json"),
),
"bot-bottle-demo-abc12",
)
cp.assert_called_once_with(
@@ -269,6 +297,7 @@ class TestProvisionProviderAuth(unittest.TestCase):
ex.return_value = SmolvmRunResult(0, "Logged in using ChatGPT\n", "")
_provider_auth.provision_provider_auth(
_plan(
agent_provider_template="codex",
codex_auth_file=Path("/tmp/codex-auth.json"),
guest_env={"CODEX_HOME": "/run/codex-home"},
),
@@ -297,6 +326,7 @@ class TestProvisionProviderAuth(unittest.TestCase):
with self.assertRaises(SystemExit):
_provider_auth.provision_provider_auth(
_plan(
agent_provider_template="codex",
codex_auth_file=Path("/tmp/codex-auth.json"),
),
"bot-bottle-demo-abc12",
@@ -313,6 +343,9 @@ class TestProvisionProviderAuth(unittest.TestCase):
SmolvmRunResult(0, "", ""), # chown CODEX_HOME
SmolvmRunResult(0, "", ""), # chmod CODEX_HOME
SmolvmRunResult(0, "", ""), # reset runtime db files
SmolvmRunResult(0, "", ""), # write config.toml
SmolvmRunResult(0, "", ""), # chown config.toml
SmolvmRunResult(0, "", ""), # chmod config.toml
SmolvmRunResult(0, "", ""), # chown auth.json
SmolvmRunResult(0, "", ""), # chmod auth.json
SmolvmRunResult(1, "Not logged in\n", ""), # login status
@@ -320,6 +353,7 @@ class TestProvisionProviderAuth(unittest.TestCase):
with self.assertRaises(SystemExit):
_provider_auth.provision_provider_auth(
_plan(
agent_provider_template="codex",
codex_auth_file=Path("/tmp/codex-auth.json"),
),
"bot-bottle-demo-abc12",