The spawn path kept its own arg list and never got the --settings payload, so a freshly spawned run had neither the stop guard nor the inbox opt-in (its background shell died at once, /message was accepted by the socket and dropped). _claude_args builds the shared part for all three launch paths; a test pins that no literal arg list is left. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
62 lines
2.4 KiB
Python
62 lines
2.4 KiB
Python
"""Every claude launch path builds on _claude_args: the --settings payload
|
|
(stop guard hook + inbox opt-in) and the headless flags can't be missed by
|
|
one of spawn / resume / fork — the spawn path once was."""
|
|
|
|
import json
|
|
import re
|
|
|
|
import sidecar
|
|
|
|
SID = "11111111-2222-3333-4444-555555555555"
|
|
|
|
|
|
def _settings(args):
|
|
assert args.count("--settings") == 1
|
|
return json.loads(args[args.index("--settings") + 1])
|
|
|
|
|
|
def test_spawn_and_resume_shapes_share_the_flags():
|
|
spawn = sidecar._claude_args("hi", "--session-id", SID)
|
|
resume = sidecar._claude_args("hi", "--resume", SID)
|
|
assert spawn[:5] == [sidecar.CLAUDE_BIN, "-p", "hi", "--session-id", SID]
|
|
assert resume[:5] == [sidecar.CLAUDE_BIN, "-p", "hi", "--resume", SID]
|
|
for a in (spawn, resume):
|
|
assert a[a.index("--output-format") + 1] == "json"
|
|
assert a[a.index("--permission-mode") + 1] == sidecar.PERMISSION_MODE
|
|
if sidecar.DISALLOWED_TOOLS:
|
|
assert " ".join(sidecar.DISALLOWED_TOOLS) in a
|
|
s = _settings(a)
|
|
assert s["crossSessionInbound"] == "accept"
|
|
|
|
|
|
def test_settings_carry_the_stop_guard_hook():
|
|
s = _settings(sidecar._claude_args("hi", "--session-id", SID))
|
|
hooks = s["hooks"]["Stop"][0]["hooks"]
|
|
assert len(hooks) == 1
|
|
cmd = hooks[0]["command"]
|
|
assert cmd.endswith("stop_guard.py") and "python" in cmd
|
|
assert hooks[0]["timeout"] == sidecar.STOP_GUARD_TIMEOUT_S
|
|
assert hooks[0]["type"] == "command"
|
|
|
|
|
|
def test_stop_guard_can_be_switched_off(monkeypatch):
|
|
monkeypatch.setattr(sidecar, "STOP_GUARD_ENABLED", False)
|
|
s = _settings(sidecar._claude_args("hi", "--session-id", SID))
|
|
assert "hooks" not in s and s["crossSessionInbound"] == "accept"
|
|
|
|
|
|
def test_every_launch_site_uses_the_builder():
|
|
src = open(sidecar.__file__, encoding="utf-8").read()
|
|
# the only literal `CLAUDE_BIN, "-p"` is the builder's own
|
|
assert len(re.findall(r'CLAUDE_BIN, "-p"', src)) == 1
|
|
assert src.count("_claude_args(prompt, ") == 3 # spawn, resume, fork
|
|
|
|
|
|
def test_run_env_exports_the_wait_ceiling(monkeypatch):
|
|
monkeypatch.setattr(sidecar, "BG_WAIT_CEILING_MS", "7200000")
|
|
env = sidecar._run_env(SID, None)
|
|
assert env["CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS"] == "7200000"
|
|
monkeypatch.setattr(sidecar, "BG_WAIT_CEILING_MS", "")
|
|
monkeypatch.delenv("CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS", raising=False)
|
|
assert "CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS" not in sidecar._run_env(SID, None)
|