Skip to content

Commit d15c903

Browse files
aboucaudclaude
andcommitted
fix(launcher): harden harness install (entrypoint override, OCI tag, session-env, npm noise)
Fixes found during smoke testing: - Add --entrypoint sh to bypass ENTRYPOINT ["bash"] in the install container - Sanitize lc_version for OCI tags: replace '+' with '-' (PEP 440 local versions) - Mount .claude/session-env/ so SessionStart hook can write inside the container - Suppress npm output with --loglevel=error --no-update-notifier Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6bff004 commit d15c903

2 files changed

Lines changed: 36 additions & 7 deletions

File tree

src/lightcone/engine/launcher.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,16 @@ def _make_builtin_targets() -> dict[str, LaunchTarget]:
132132
".claude/settings.json",
133133
".claude/settings.local.json",
134134
".claude/keybindings.json",
135+
".claude/session-env/",
135136
],
136137
# Claude Code refuses --dangerously-skip-permissions as root;
137138
# running as the host UID/GID also ensures correct ownership on
138139
# the mounted project directory.
139140
run_as_host_user=True,
140141
registry_name=_SANDBOX_IMAGE_NAME,
141-
install_cmds=["npm install -g @anthropic-ai/claude-code"],
142+
install_cmds=[
143+
"npm install -g @anthropic-ai/claude-code --loglevel=error --no-update-notifier"
144+
],
142145
committed_tag_prefix="lightcone-claude",
143146
),
144147
"mistral-vibe": LaunchTarget(
@@ -179,7 +182,7 @@ def _make_builtin_targets() -> dict[str, LaunchTarget]:
179182
".config/opencode/AGENTS.md",
180183
],
181184
registry_name=_SANDBOX_IMAGE_NAME,
182-
install_cmds=["npm install -g opencode-ai"],
185+
install_cmds=["npm install -g opencode-ai --loglevel=error --no-update-notifier"],
183186
committed_tag_prefix="lightcone-opencode",
184187
),
185188
}
@@ -433,7 +436,9 @@ def _ensure_harness_image(
433436
result as ``<committed_tag_prefix>:<lc_version>``, and removes the temp
434437
container. On subsequent calls the existing committed image is reused.
435438
"""
436-
committed_tag = f"{target.committed_tag_prefix}:{lc_version}"
439+
# OCI tags allow [a-zA-Z0-9_.-] only — replace '+' from PEP 440 local versions.
440+
safe_version = lc_version.replace("+", "-")
441+
committed_tag = f"{target.committed_tag_prefix}:{safe_version}"
437442

438443
if not reinstall and _image_exists(committed_tag, runtime):
439444
return committed_tag
@@ -447,7 +452,8 @@ def _ensure_harness_image(
447452
_print(f"Installing {target.name} harness (first run — this may take a few minutes)…")
448453
try:
449454
subprocess.run(
450-
[runtime, "run", "--name", tmp_name, base_image, "sh", "-c", install_cmd],
455+
[runtime, "run", "--entrypoint", "sh", "--name", tmp_name,
456+
base_image, "-c", install_cmd],
451457
check=True,
452458
)
453459
subprocess.run(

tests/test_launcher.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,12 @@ def test_claude_target_fields(self) -> None:
5757
assert "CLAUDE_CODE_OAUTH_TOKEN" in t.env_passthrough
5858
assert "/dev/fuse" in t.devices
5959
assert ".claude.json" in t.home_mounts
60+
assert ".claude/session-env/" in t.home_mounts
6061
assert t.run_as_host_user is True
6162
assert t.registry_name == "lightcone-sandbox"
62-
assert t.install_cmds == ["npm install -g @anthropic-ai/claude-code"]
63+
assert t.install_cmds == [
64+
"npm install -g @anthropic-ai/claude-code --loglevel=error --no-update-notifier"
65+
]
6366
assert t.committed_tag_prefix == "lightcone-claude"
6467

6568
def test_mistral_vibe_is_registered(self) -> None:
@@ -85,7 +88,9 @@ def test_opencode_is_registered(self) -> None:
8588
def test_opencode_target_fields(self) -> None:
8689
t = BUILTIN_TARGETS["opencode"]
8790
assert t.name == "opencode"
88-
assert t.install_cmds == ["npm install -g opencode-ai"]
91+
assert t.install_cmds == [
92+
"npm install -g opencode-ai --loglevel=error --no-update-notifier"
93+
]
8994
assert t.committed_tag_prefix == "lightcone-opencode"
9095
assert t.entrypoint == ["opencode"]
9196
assert "OPENAI_API_KEY" in t.env_passthrough
@@ -1165,6 +1170,22 @@ def test_returns_committed_tag_when_image_exists(
11651170
assert result == "lightcone-claude:1.2.3"
11661171
mock_run.assert_not_called()
11671172

1173+
def test_plus_in_version_replaced_for_oci_tag(
1174+
self, harness_target: LaunchTarget
1175+
) -> None:
1176+
from lightcone.engine.launcher import _ensure_harness_image
1177+
1178+
# PEP 440 local versions (e.g. 0.3.5.dev33+g8e1ae4ad0) contain '+' which
1179+
# is not valid in OCI image tags — must be replaced with '-'.
1180+
dev_version = "0.3.5.dev33+g8e1ae4ad0"
1181+
with patch("lightcone.engine.launcher._image_exists", return_value=True):
1182+
result = _ensure_harness_image(
1183+
harness_target, "lc-lightcone-sandbox-abc", "docker", dev_version
1184+
)
1185+
1186+
assert "+" not in result
1187+
assert result == "lightcone-claude:0.3.5.dev33-g8e1ae4ad0"
1188+
11681189
def test_installs_and_commits_when_image_absent(
11691190
self, harness_target: LaunchTarget
11701191
) -> None:
@@ -1179,9 +1200,11 @@ def test_installs_and_commits_when_image_absent(
11791200

11801201
assert result == "lightcone-claude:1.2.3"
11811202
calls = [c[0][0] for c in mock_run.call_args_list]
1182-
# First call: docker run (install)
1203+
# First call: docker run (install) — must use --entrypoint sh to bypass ENTRYPOINT ["bash"]
11831204
assert calls[0][0] == "docker"
11841205
assert calls[0][1] == "run"
1206+
assert "--entrypoint" in calls[0]
1207+
assert calls[0][calls[0].index("--entrypoint") + 1] == "sh"
11851208
assert "npm install -g @anthropic-ai/claude-code" in calls[0]
11861209
# Second call: docker commit
11871210
assert calls[1][0] == "docker"

0 commit comments

Comments
 (0)