Constrain controlled CLI sessions and prove native budget overshoot
Some checks failed
Governed runtime contract / contract (push) Failing after 27s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-09 20:53:25 +02:00
parent e0b3ff99a2
commit 4ae245a88f
14 changed files with 752 additions and 10 deletions

View file

@ -3,11 +3,11 @@
llm-connect's ClaudeCodeAdapter is a text-generation adapter (`claude
--print`, no working directory, no tool grants). An executor run needs an
*agentic* session: file edits and git commits inside the target repo, under
a hard tool allow-list. This adapter subclasses it, keeping the llm-connect
registered tool permission rules. This adapter subclasses it, keeping the llm-connect
LLMAdapter interface so a hosted adapter can be swapped in later, and adds:
- cwd pinned to the target repo
- --permission-mode acceptEdits
- acceptEdits for legacy sessions; dontAsk and an explicit tool inventory for controlled runs
- allow-list from a named tool profile (default: green-commit-only)
- optional real-time per-tool-call audit events (HARNESS-WP-0002-T03)
@ -133,6 +133,17 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
cmd += ["--max-budget-usd", str(budget)]
if turns is not None:
cmd += ["--max-turns", str(turns)]
if self._native_config != (None, None):
# A permission allow rule is not a tool inventory. Controlled runs
# use only the registered tools and deny every unapproved operation.
cmd[cmd.index("--permission-mode") + 1] = "dontAsk"
cmd += [
"--bare", "--setting-sources", "",
"--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
"--disallowedTools", "mcp__*",
"--tools", self._profile.available_tools,
"--no-session-persistence",
]
if self._model:
cmd.extend(["--model", self._model])
return cmd
@ -143,8 +154,8 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
if self._native_config != (None, None) and not self._native_cli_checked:
check = subprocess.run([self._cli_path, "--version"], capture_output=True, text=True, timeout=10, cwd=self._workdir)
version = re.match(r"(\d+)\.(\d+)\.(\d+)(?:\s|$)", check.stdout.strip())
if check.returncode or not version or tuple(map(int, version.groups())) < (2, 1, 217):
raise NativeLimitError("native limits require verified Claude Code 2.1.217 or newer")
if check.returncode or not version or tuple(map(int, version.groups())) < (2, 1, 266):
raise NativeLimitError("native limits require verified Claude Code 2.1.266 or newer")
self._native_cli_checked = True
timeout = config.timeout_seconds or self._config.timeout_seconds
if self._on_tool_event is not None: