config / pids / validate / claude_accounts / launch / fork / routes_runs /
routes_accounts, mounted by a thin sidecar.py (same `sidecar:app`, same 24
routes, same startup hook). Mutable state keeps one owner module
(pids._procs, claude_accounts._account_status_cache). Includes the
_claude_args builder from 1de9426 and its test; the Dockerfile's explicit
COPY list gains the new modules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
72 lines
2.9 KiB
Python
72 lines
2.9 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 claude_accounts
|
|
import fork
|
|
import launch
|
|
import routes_runs
|
|
from config import CLAUDE_BIN, DISALLOWED_TOOLS, PERMISSION_MODE, STOP_GUARD_TIMEOUT_S
|
|
from launch import _claude_args
|
|
|
|
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 = _claude_args("hi", "--session-id", SID)
|
|
resume = _claude_args("hi", "--resume", SID)
|
|
assert spawn[:5] == [CLAUDE_BIN, "-p", "hi", "--session-id", SID]
|
|
assert resume[:5] == [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] == PERMISSION_MODE
|
|
if DISALLOWED_TOOLS:
|
|
assert " ".join(DISALLOWED_TOOLS) in a
|
|
s = _settings(a)
|
|
assert s["crossSessionInbound"] == "accept"
|
|
|
|
|
|
def test_settings_carry_the_stop_guard_hook():
|
|
s = _settings(_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"] == STOP_GUARD_TIMEOUT_S
|
|
assert hooks[0]["type"] == "command"
|
|
|
|
|
|
def test_stop_guard_can_be_switched_off(monkeypatch):
|
|
# _claude_settings reads the flag from launch's own globals (it is bound
|
|
# there at import), so that is the module to patch — not config.
|
|
monkeypatch.setattr(launch, "STOP_GUARD_ENABLED", False)
|
|
s = _settings(_claude_args("hi", "--session-id", SID))
|
|
assert "hooks" not in s and s["crossSessionInbound"] == "accept"
|
|
|
|
|
|
def test_every_launch_site_uses_the_builder():
|
|
# The builder lives in launch.py; the three launch sites are spawn and
|
|
# resume (routes_runs.py) and fork (fork.py).
|
|
src = "".join(open(m.__file__, encoding="utf-8").read()
|
|
for m in (launch, routes_runs, fork))
|
|
# 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(claude_accounts, "BG_WAIT_CEILING_MS", "7200000")
|
|
env = claude_accounts._run_env(SID, None)
|
|
assert env["CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS"] == "7200000"
|
|
monkeypatch.setattr(claude_accounts, "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 claude_accounts._run_env(SID, None)
|