alishahryar1--free-claude-code
aba94d5c3c
## Problem Claude proxy environment policy was duplicated between `fcc-claude`, managed messaging, and live smoke drivers. Managed messaging preserved a legacy endpoint variable, inherited more Anthropic state, and did not disable nonessential traffic, so FCC-launched Claude sessions could drift apart. ## Changes | Before | After | | --- | --- | | `fcc-claude` and managed messaging assembled proxy environments independently. | One shared owner strips inherited Anthropic variables and configures proxy URL, auth, discovery, compaction, and nonessential-traffic policy. | | Managed messaging carried a `/v1` API URL and converted it back to a proxy root while also setting a legacy endpoint variable. | Managed messaging receives the loopback-safe proxy root and uses the supported `ANTHROPIC_BASE_URL` contract directly. | | Managed and interactive policy could diverge while smoke drivers duplicated both shapes. | `fcc-claude`, messaging, and Claude smoke drivers use the same canonical environment builder. | | Managed execution concerns were mixed with shared proxy policy. | Messaging adds only noninteractive process settings and keeps `--model opus` plus stream-JSON flags in command construction. | | IDE examples left nonessential Anthropic traffic enabled. | VS Code and JetBrains examples disable nonessential Anthropic traffic. | <!-- greptile_comment --> <details open><summary><h3>Greptile Summary</h3></summary> This PR centralizes Claude Code proxy environment setup for FCC-launched sessions. The main changes are: - Adds one shared builder for Claude proxy environment variables. - Routes managed messaging sessions through the same proxy policy as `fcc-claude`. - Strips inherited `ANTHROPIC_*` state before launching Claude. - Sets the nonessential-traffic disable flag for managed and interactive Claude launches. - Updates smoke tests, docs, and version metadata for the new proxy-root URL shape. </details> <h3>Confidence Score: 5/5</h3> This looks safe to merge. No blocking issues were found in the changed code. Managed Claude launches now use the shared environment builder, and the managed path now sets the nonessential-traffic disable flag. No files need attention. <details><summary><h3><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="20" align="absmiddle"></a> T-Rex Logs</h3></summary> **What T-Rex did** - The executable harness claude\_env\_policy\_harness.py was generated to enable direct module-level runtime proof without requiring a real Claude binary or live Anthropic credentials. - A focused pytest run was executed, and it completed with 14 tests passing and exit code 0, validating the harness workflow. <a href="https://app.greptile.com/trex/runs/14173386/artifacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img alt="View all artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a> <sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub> </details> <details open><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | src/free_claude_code/cli/claude_env.py | Adds the shared Claude proxy environment builder and canonical traffic-disable policy. | | src/free_claude_code/cli/managed/claude.py | Delegates managed Claude environment construction to the shared proxy builder. | | src/free_claude_code/runtime/application.py | Passes the loopback-safe proxy root into the managed Claude session manager. | | src/free_claude_code/cli/managed/session.py | Renames managed session URL state to use the proxy-root contract. | | src/free_claude_code/cli/managed/manager.py | Carries the proxy-root URL through manager-created managed sessions. | </details> <sub>Reviews (2): Last reviewed commit: ["Keep README focused on client setup"](https://github.com/alishahryar1/free-claude-code/commit/83739a2f8953771ec9e447fc83a2057b08708891) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=43684511)</sub> <!-- /greptile_comment -->
787 行
23 KiB
Python
787 行
23 KiB
Python
"""Claude Code CLI characterization helpers for provider smoke matrices."""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import time
|
|
import uuid
|
|
from dataclasses import asdict, dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from free_claude_code.cli.claude_env import build_claude_proxy_env
|
|
from smoke.lib.child_process import run_captured_text
|
|
from smoke.lib.config import ProviderModel, SmokeConfig, redacted
|
|
from smoke.lib.server import RunningServer
|
|
|
|
REGRESSION_CLASSIFICATIONS = frozenset({"harness_bug", "product_failure"})
|
|
|
|
_HTTP_REGRESSION_PATTERNS = (
|
|
r'POST /v1/messages[^"\n]* HTTP/1\.1" 4(?!01|03|04|08|09)\d\d',
|
|
r'POST /v1/messages[^"\n]* HTTP/1\.1" 5\d\d',
|
|
)
|
|
_UPSTREAM_UNAVAILABLE_MARKERS = (
|
|
"upstream_unavailable",
|
|
"readtimeout",
|
|
"connecterror",
|
|
"connection refused",
|
|
"timed out",
|
|
"rate limit",
|
|
"overloaded",
|
|
"capacity",
|
|
"upstream provider",
|
|
"provider api request failed",
|
|
"httpstatuserror",
|
|
)
|
|
_HTTP_429_PATTERNS = (
|
|
r'HTTP/1\.[01]" 429\b',
|
|
r"\bHTTP/1\.[01] 429\b",
|
|
r"\bstatus_code=429\b",
|
|
r"\bstatus[=:]\s*429\b",
|
|
r"\b429 Too Many Requests\b",
|
|
)
|
|
_MISSING_ENV_MARKERS = (
|
|
"api key",
|
|
"not logged in",
|
|
"authentication",
|
|
"permission denied",
|
|
)
|
|
_EMPTY_MCP_CONFIG = '{"mcpServers":{}}'
|
|
_SUBAGENT_SYSTEM_PROMPT = (
|
|
"You are a deterministic smoke-test coordinator. Use Agent when asked to "
|
|
"use a subagent."
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ClaudeCliRun:
|
|
command: tuple[str, ...]
|
|
returncode: int | None
|
|
stdout: str
|
|
stderr: str
|
|
duration_s: float
|
|
timed_out: bool = False
|
|
|
|
@property
|
|
def combined_output(self) -> str:
|
|
return f"{self.stdout}\n{self.stderr}"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class CliMatrixOutcome:
|
|
model: str
|
|
full_model: str
|
|
source: str
|
|
feature: str
|
|
outcome: str
|
|
classification: str
|
|
duration_s: float
|
|
cli_returncode: int | None
|
|
token_evidence: dict[str, Any]
|
|
request_count: int
|
|
log_path: str
|
|
stdout_excerpt: str
|
|
stderr_excerpt: str
|
|
log_excerpt: str
|
|
|
|
|
|
def run_claude_cli(
|
|
*,
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
config: SmokeConfig,
|
|
cwd: Path,
|
|
prompt: str,
|
|
tools: str | None,
|
|
bare: bool = True,
|
|
pre_tool_args: tuple[str, ...] = (),
|
|
extra_args: tuple[str, ...] = (),
|
|
session_id: str | None = None,
|
|
resume_session_id: str | None = None,
|
|
no_session_persistence: bool = True,
|
|
) -> ClaudeCliRun:
|
|
"""Run Claude Code CLI against the local smoke proxy."""
|
|
cwd.mkdir(parents=True, exist_ok=True)
|
|
|
|
cmd = list(
|
|
_build_claude_cli_command(
|
|
claude_bin=claude_bin,
|
|
prompt=prompt,
|
|
tools=tools,
|
|
bare=bare,
|
|
pre_tool_args=pre_tool_args,
|
|
extra_args=extra_args,
|
|
session_id=session_id,
|
|
resume_session_id=resume_session_id,
|
|
no_session_persistence=no_session_persistence,
|
|
)
|
|
)
|
|
|
|
env = build_claude_proxy_env(
|
|
proxy_root_url=server.base_url,
|
|
auth_token=config.settings.anthropic_auth_token,
|
|
base_env=os.environ,
|
|
)
|
|
env["TERM"] = "dumb"
|
|
env["NO_COLOR"] = "1"
|
|
env["PYTHONIOENCODING"] = "utf-8"
|
|
|
|
started = time.monotonic()
|
|
try:
|
|
result = run_captured_text(
|
|
cmd,
|
|
cwd=cwd,
|
|
env=env,
|
|
timeout=config.timeout_s,
|
|
check=False,
|
|
)
|
|
except subprocess.TimeoutExpired as exc:
|
|
return ClaudeCliRun(
|
|
command=tuple(cmd),
|
|
returncode=None,
|
|
stdout=_coerce_timeout_text(exc.stdout),
|
|
stderr=_coerce_timeout_text(exc.stderr),
|
|
duration_s=time.monotonic() - started,
|
|
timed_out=True,
|
|
)
|
|
|
|
return ClaudeCliRun(
|
|
command=tuple(cmd),
|
|
returncode=result.returncode,
|
|
stdout=_coerce_timeout_text(result.stdout),
|
|
stderr=_coerce_timeout_text(result.stderr),
|
|
duration_s=time.monotonic() - started,
|
|
)
|
|
|
|
|
|
def _build_claude_cli_command(
|
|
*,
|
|
claude_bin: str,
|
|
prompt: str,
|
|
tools: str | None,
|
|
bare: bool = True,
|
|
pre_tool_args: tuple[str, ...] = (),
|
|
extra_args: tuple[str, ...] = (),
|
|
session_id: str | None = None,
|
|
resume_session_id: str | None = None,
|
|
no_session_persistence: bool = True,
|
|
) -> tuple[str, ...]:
|
|
cmd: list[str] = [claude_bin]
|
|
if bare:
|
|
cmd.append("--bare")
|
|
if resume_session_id:
|
|
cmd.extend(["--resume", resume_session_id])
|
|
if session_id:
|
|
cmd.extend(["--session-id", session_id])
|
|
cmd.extend(
|
|
[
|
|
"--output-format",
|
|
"stream-json",
|
|
"--include-partial-messages",
|
|
"--verbose",
|
|
"--permission-mode",
|
|
"bypassPermissions",
|
|
"--dangerously-skip-permissions",
|
|
"--model",
|
|
"sonnet",
|
|
]
|
|
)
|
|
if no_session_persistence:
|
|
cmd.append("--no-session-persistence")
|
|
cmd.extend(pre_tool_args)
|
|
if tools is not None:
|
|
cmd.extend(["--tools", tools])
|
|
if tools:
|
|
cmd.extend(["--allowedTools", tools])
|
|
cmd.extend(extra_args)
|
|
cmd.extend(["-p", prompt])
|
|
return tuple(cmd)
|
|
|
|
|
|
def run_cli_feature_probes(
|
|
*,
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> list[CliMatrixOutcome]:
|
|
return [
|
|
_basic_text(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
_thinking(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
_tool_use_roundtrip(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
_interleaved_thinking_tool(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
_subagent_task(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
_compact_command(
|
|
claude_bin, server, smoke_config, provider_model, model_dir, marker_prefix
|
|
),
|
|
]
|
|
|
|
|
|
def read_log_offset(log_path: Path) -> int:
|
|
"""Return the current text length of a smoke server log."""
|
|
if not log_path.is_file():
|
|
return 0
|
|
return len(log_path.read_text(encoding="utf-8", errors="replace"))
|
|
|
|
|
|
def read_log_delta(log_path: Path, offset: int) -> str:
|
|
"""Return smoke server log text written after ``offset``."""
|
|
if not log_path.is_file():
|
|
return ""
|
|
text = log_path.read_text(encoding="utf-8", errors="replace")
|
|
return text[offset:]
|
|
|
|
|
|
def token_evidence(
|
|
*,
|
|
feature: str,
|
|
marker: str,
|
|
run: ClaudeCliRun,
|
|
log_delta: str,
|
|
) -> dict[str, Any]:
|
|
"""Collect compact evidence for a CLI feature probe."""
|
|
combined = f"{run.combined_output}\n{log_delta}"
|
|
lower = combined.lower()
|
|
return {
|
|
"feature": feature,
|
|
"marker_present": bool(marker and marker in combined),
|
|
"thinking_delta_count": combined.count("thinking_delta"),
|
|
"tool_use_count": combined.count('"tool_use"'),
|
|
"tool_result_count": combined.count('"tool_result"'),
|
|
"agent_catalog_present": _tool_catalog_has(log_delta, "Agent"),
|
|
"agent_tool_count": _agent_tool_count(combined),
|
|
"agent_result_count": _agent_result_count(combined),
|
|
"task_tool_count": combined.count('"name": "Task"')
|
|
+ combined.count('"name":"Task"'),
|
|
"run_in_background_false": "run_in_background" in combined and "false" in lower,
|
|
"compact_boundary": "compact_boundary" in combined,
|
|
"compact_metadata": "compact_metadata" in combined,
|
|
"http_422": 'HTTP/1.1" 422' in combined,
|
|
"http_500": bool(re.search(r'HTTP/1\.1" 5\d\d', combined)),
|
|
"timed_out": run.timed_out,
|
|
}
|
|
|
|
|
|
def classify_probe(
|
|
*,
|
|
run: ClaudeCliRun,
|
|
log_delta: str,
|
|
marker: str,
|
|
requires_tool_result: bool = False,
|
|
requires_agent: bool = False,
|
|
requires_task: bool = False,
|
|
requires_compact: bool = False,
|
|
) -> tuple[str, str]:
|
|
"""Classify a probe without failing compatibility characterization failures."""
|
|
combined = f"{run.combined_output}\n{log_delta}"
|
|
lower = combined.lower()
|
|
|
|
if _has_proxy_regression(log_delta):
|
|
return "failed", "product_failure"
|
|
if run.returncode != 0 and any(
|
|
marker_text in lower for marker_text in _MISSING_ENV_MARKERS
|
|
):
|
|
return "skipped", "missing_env"
|
|
if run.timed_out:
|
|
return "failed", "probe_timeout"
|
|
if requires_agent and not _tool_catalog_has(log_delta, "Agent"):
|
|
return "failed", "harness_bug"
|
|
|
|
marker_ok = not marker or marker in combined
|
|
tool_ok = not requires_tool_result or '"tool_result"' in combined
|
|
agent_ok = not requires_agent or (
|
|
_agent_tool_count(combined) > 0 and _agent_result_count(combined) > 0
|
|
)
|
|
task_ok = not requires_task or (
|
|
('"name": "Task"' in combined or '"name":"Task"' in combined)
|
|
and "run_in_background" in combined
|
|
and "false" in lower
|
|
)
|
|
compact_ok = not requires_compact or (
|
|
"compact_boundary" in combined
|
|
or "compact_metadata" in combined
|
|
or "/compact" in combined
|
|
or "compact" in lower
|
|
)
|
|
cli_ok = run.returncode == 0
|
|
|
|
if cli_ok and marker_ok and tool_ok and agent_ok and task_ok and compact_ok:
|
|
return "passed", "passed"
|
|
if _has_upstream_unavailable_text(combined):
|
|
return "failed", "upstream_unavailable"
|
|
if not _has_proxy_request(log_delta):
|
|
return "failed", "harness_bug"
|
|
return "failed", "model_feature_failure"
|
|
|
|
|
|
def make_outcome(
|
|
*,
|
|
model: str,
|
|
full_model: str,
|
|
source: str,
|
|
feature: str,
|
|
marker: str,
|
|
run: ClaudeCliRun,
|
|
log_delta: str,
|
|
log_path: Path,
|
|
requires_tool_result: bool = False,
|
|
requires_agent: bool = False,
|
|
requires_task: bool = False,
|
|
requires_compact: bool = False,
|
|
) -> CliMatrixOutcome:
|
|
"""Build one report outcome from a CLI run and its server log delta."""
|
|
outcome, classification = classify_probe(
|
|
run=run,
|
|
log_delta=log_delta,
|
|
marker=marker,
|
|
requires_tool_result=requires_tool_result,
|
|
requires_agent=requires_agent,
|
|
requires_task=requires_task,
|
|
requires_compact=requires_compact,
|
|
)
|
|
evidence = token_evidence(
|
|
feature=feature,
|
|
marker=marker,
|
|
run=run,
|
|
log_delta=log_delta,
|
|
)
|
|
return CliMatrixOutcome(
|
|
model=model,
|
|
full_model=full_model,
|
|
source=source,
|
|
feature=feature,
|
|
outcome=outcome,
|
|
classification=classification,
|
|
duration_s=round(run.duration_s, 3),
|
|
cli_returncode=run.returncode,
|
|
token_evidence=evidence,
|
|
request_count=_request_count(log_delta),
|
|
log_path=str(log_path),
|
|
stdout_excerpt=_excerpt(run.stdout),
|
|
stderr_excerpt=_excerpt(run.stderr),
|
|
log_excerpt=_excerpt(log_delta),
|
|
)
|
|
|
|
|
|
def write_matrix_report(
|
|
config: SmokeConfig,
|
|
outcomes: list[CliMatrixOutcome],
|
|
*,
|
|
target: str,
|
|
filename_prefix: str,
|
|
) -> Path:
|
|
"""Write a Claude CLI compatibility matrix report."""
|
|
config.results_dir.mkdir(parents=True, exist_ok=True)
|
|
path = (
|
|
config.results_dir
|
|
/ f"{filename_prefix}-matrix-{config.worker_id}-{int(time.time())}.json"
|
|
)
|
|
payload = {
|
|
"started_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
"worker_id": config.worker_id,
|
|
"target": target,
|
|
"models": sorted({outcome.full_model for outcome in outcomes}),
|
|
"outcomes": [asdict(outcome) for outcome in outcomes],
|
|
}
|
|
path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def regression_failures(outcomes: list[CliMatrixOutcome]) -> list[str]:
|
|
"""Return report lines for classifications that should fail pytest."""
|
|
return [
|
|
f"{outcome.full_model} {outcome.feature}: {outcome.classification}"
|
|
for outcome in outcomes
|
|
if outcome.classification in REGRESSION_CLASSIFICATIONS
|
|
]
|
|
|
|
|
|
def _basic_text(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "BASIC")
|
|
return _run_probe(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
smoke_config=smoke_config,
|
|
provider_model=provider_model,
|
|
workspace=model_dir / "basic_text",
|
|
feature="basic_text",
|
|
marker=marker,
|
|
prompt=f"Reply with exactly {marker} and no other text.",
|
|
tools="",
|
|
)
|
|
|
|
|
|
def _thinking(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "THINK")
|
|
return _run_probe(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
smoke_config=smoke_config,
|
|
provider_model=provider_model,
|
|
workspace=model_dir / "thinking",
|
|
feature="thinking",
|
|
marker=marker,
|
|
prompt=(
|
|
"Think privately about the request, then reply with exactly "
|
|
f"{marker} and no other text."
|
|
),
|
|
tools="",
|
|
extra_args=("--effort", "high"),
|
|
)
|
|
|
|
|
|
def _tool_use_roundtrip(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "TOOL")
|
|
workspace = model_dir / "tool_use_roundtrip"
|
|
(workspace / "smoke-read.txt").parent.mkdir(parents=True, exist_ok=True)
|
|
(workspace / "smoke-read.txt").write_text(marker, encoding="utf-8")
|
|
return _run_probe(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
smoke_config=smoke_config,
|
|
provider_model=provider_model,
|
|
workspace=workspace,
|
|
feature="tool_use_roundtrip",
|
|
marker=marker,
|
|
prompt=(
|
|
"Use the Read tool to read smoke-read.txt. Reply with exactly the "
|
|
"secret token from that file and no other text."
|
|
),
|
|
tools="Read",
|
|
requires_tool_result=True,
|
|
)
|
|
|
|
|
|
def _interleaved_thinking_tool(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "INTERLEAVED")
|
|
workspace = model_dir / "interleaved_thinking_tool"
|
|
(workspace / "smoke-interleaved.txt").parent.mkdir(parents=True, exist_ok=True)
|
|
(workspace / "smoke-interleaved.txt").write_text(marker, encoding="utf-8")
|
|
return _run_probe(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
smoke_config=smoke_config,
|
|
provider_model=provider_model,
|
|
workspace=workspace,
|
|
feature="interleaved_thinking_tool",
|
|
marker=marker,
|
|
prompt=(
|
|
"Think privately, use Read on smoke-interleaved.txt, then reply with "
|
|
"exactly the secret token from that file and no other text."
|
|
),
|
|
tools="Read",
|
|
extra_args=("--effort", "high"),
|
|
requires_tool_result=True,
|
|
)
|
|
|
|
|
|
def _subagent_task(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "TASK")
|
|
workspace = model_dir / "subagent_task"
|
|
(workspace / "smoke-subagent.txt").parent.mkdir(parents=True, exist_ok=True)
|
|
(workspace / "smoke-subagent.txt").write_text(marker, encoding="utf-8")
|
|
agents = json.dumps(
|
|
{
|
|
"smoke_reader": {
|
|
"description": "Reads one requested file and returns its token.",
|
|
"prompt": (
|
|
"Read the requested file with Read and return only the token "
|
|
"inside it."
|
|
),
|
|
"tools": ["Read"],
|
|
"permissionMode": "bypassPermissions",
|
|
"background": False,
|
|
}
|
|
}
|
|
)
|
|
bare, tools, pre_tool_args, extra_args = _subagent_probe_options(agents)
|
|
return _run_probe(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
smoke_config=smoke_config,
|
|
provider_model=provider_model,
|
|
workspace=workspace,
|
|
feature="subagent_task",
|
|
marker=marker,
|
|
prompt=(
|
|
"Use the smoke_reader subagent to read smoke-subagent.txt. After the "
|
|
"first agent result, reply with exactly the token and stop. Do not "
|
|
"call any other tools."
|
|
),
|
|
tools=tools,
|
|
bare=bare,
|
|
pre_tool_args=pre_tool_args,
|
|
extra_args=extra_args,
|
|
requires_tool_result=True,
|
|
requires_agent=True,
|
|
)
|
|
|
|
|
|
def _subagent_probe_options(
|
|
agents: str,
|
|
) -> tuple[bool, str, tuple[str, ...], tuple[str, ...]]:
|
|
return (
|
|
False,
|
|
"Agent,Read",
|
|
(
|
|
"--setting-sources",
|
|
"local",
|
|
"--strict-mcp-config",
|
|
"--mcp-config",
|
|
_EMPTY_MCP_CONFIG,
|
|
"--system-prompt",
|
|
_SUBAGENT_SYSTEM_PROMPT,
|
|
),
|
|
("--agents", agents),
|
|
)
|
|
|
|
|
|
def _compact_command(
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
model_dir: Path,
|
|
marker_prefix: str,
|
|
) -> CliMatrixOutcome:
|
|
marker = _marker(marker_prefix, "COMPACT")
|
|
workspace = model_dir / "compact_command"
|
|
session_id = str(uuid.uuid4())
|
|
offset = read_log_offset(server.log_path)
|
|
first = run_claude_cli(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
config=smoke_config,
|
|
cwd=workspace,
|
|
prompt=f"Remember this smoke token: {marker}. Reply with exactly {marker}.",
|
|
tools="",
|
|
session_id=session_id,
|
|
no_session_persistence=False,
|
|
)
|
|
second = run_claude_cli(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
config=smoke_config,
|
|
cwd=workspace,
|
|
prompt=f"/compact preserve {marker}",
|
|
tools="",
|
|
resume_session_id=session_id,
|
|
no_session_persistence=False,
|
|
)
|
|
log_delta = read_log_delta(server.log_path, offset)
|
|
run = ClaudeCliRun(
|
|
command=(*first.command, "&&", *second.command),
|
|
returncode=second.returncode if first.returncode == 0 else first.returncode,
|
|
stdout=f"{first.stdout}\n{second.stdout}",
|
|
stderr=f"{first.stderr}\n{second.stderr}",
|
|
duration_s=first.duration_s + second.duration_s,
|
|
timed_out=first.timed_out or second.timed_out,
|
|
)
|
|
return make_outcome(
|
|
model=provider_model.model_name,
|
|
full_model=provider_model.full_model,
|
|
source=provider_model.source,
|
|
feature="compact_command",
|
|
marker="",
|
|
run=run,
|
|
log_delta=log_delta,
|
|
log_path=server.log_path,
|
|
requires_compact=True,
|
|
)
|
|
|
|
|
|
def _run_probe(
|
|
*,
|
|
claude_bin: str,
|
|
server: RunningServer,
|
|
smoke_config: SmokeConfig,
|
|
provider_model: ProviderModel,
|
|
workspace: Path,
|
|
feature: str,
|
|
marker: str,
|
|
prompt: str,
|
|
tools: str | None,
|
|
bare: bool = True,
|
|
pre_tool_args: tuple[str, ...] = (),
|
|
extra_args: tuple[str, ...] = (),
|
|
requires_tool_result: bool = False,
|
|
requires_agent: bool = False,
|
|
requires_task: bool = False,
|
|
) -> CliMatrixOutcome:
|
|
offset = read_log_offset(server.log_path)
|
|
run = run_claude_cli(
|
|
claude_bin=claude_bin,
|
|
server=server,
|
|
config=smoke_config,
|
|
cwd=workspace,
|
|
prompt=prompt,
|
|
tools=tools,
|
|
bare=bare,
|
|
pre_tool_args=pre_tool_args,
|
|
extra_args=extra_args,
|
|
)
|
|
log_delta = read_log_delta(server.log_path, offset)
|
|
return make_outcome(
|
|
model=provider_model.model_name,
|
|
full_model=provider_model.full_model,
|
|
source=provider_model.source,
|
|
feature=feature,
|
|
marker=marker,
|
|
run=run,
|
|
log_delta=log_delta,
|
|
log_path=server.log_path,
|
|
requires_tool_result=requires_tool_result,
|
|
requires_agent=requires_agent,
|
|
requires_task=requires_task,
|
|
)
|
|
|
|
|
|
def _has_proxy_regression(log_delta: str) -> bool:
|
|
if "CREATE_MESSAGE_ERROR" in log_delta:
|
|
return True
|
|
return any(re.search(pattern, log_delta) for pattern in _HTTP_REGRESSION_PATTERNS)
|
|
|
|
|
|
def _has_proxy_request(log_delta: str) -> bool:
|
|
return (
|
|
"POST /v1/messages" in log_delta
|
|
or "API_REQUEST:" in log_delta
|
|
or '"event": "free_claude_code.api.request.received"' in log_delta
|
|
or (
|
|
'"http_method": "POST"' in log_delta
|
|
and '"http_path": "/v1/messages"' in log_delta
|
|
)
|
|
)
|
|
|
|
|
|
def _tool_catalog_has(log_delta: str, tool_name: str) -> bool:
|
|
catalog = _first_tool_catalog(log_delta)
|
|
return (
|
|
f"'name': '{tool_name}'" in catalog
|
|
or f'"name": "{tool_name}"' in catalog
|
|
or f'"name":"{tool_name}"' in catalog
|
|
)
|
|
|
|
|
|
def _first_tool_catalog(log_delta: str) -> str:
|
|
for line in log_delta.splitlines():
|
|
if "FULL_PAYLOAD" not in line:
|
|
continue
|
|
single_index = line.find("'tools': [")
|
|
double_index = line.find('"tools": [')
|
|
if single_index == -1 and double_index == -1:
|
|
continue
|
|
start = single_index if single_index != -1 else double_index
|
|
end_candidates = [
|
|
index
|
|
for marker in ("'tool_choice'", '"tool_choice"', "'thinking'", '"thinking"')
|
|
if (index := line.find(marker, start)) != -1
|
|
]
|
|
end = min(end_candidates) if end_candidates else len(line)
|
|
return line[start:end]
|
|
return ""
|
|
|
|
|
|
def _agent_tool_count(text: str) -> int:
|
|
return (
|
|
text.count('"name": "Agent"')
|
|
+ text.count('"name":"Agent"')
|
|
+ len(
|
|
re.findall(
|
|
r"'type': 'tool_use'[^}\n]+?'name': 'Agent'",
|
|
text,
|
|
flags=re.DOTALL,
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def _agent_result_count(text: str) -> int:
|
|
return text.count("agentId:") + text.count('"agentId"') + text.count("'agentId'")
|
|
|
|
|
|
def _has_upstream_unavailable_text(text: str) -> bool:
|
|
lower = text.lower()
|
|
if any(marker_text in lower for marker_text in _UPSTREAM_UNAVAILABLE_MARKERS):
|
|
return True
|
|
return any(
|
|
re.search(pattern, text, flags=re.IGNORECASE) for pattern in _HTTP_429_PATTERNS
|
|
)
|
|
|
|
|
|
def _request_count(log_delta: str) -> int:
|
|
access_log_count = log_delta.count("POST /v1/messages")
|
|
service_log_count = log_delta.count("API_REQUEST:")
|
|
structured_log_count = log_delta.count(
|
|
'"event": "free_claude_code.api.request.received"'
|
|
)
|
|
return max(access_log_count, service_log_count, structured_log_count)
|
|
|
|
|
|
def _marker(scope: str, prefix: str) -> str:
|
|
return f"FCC_{scope}_{prefix}_{uuid.uuid4().hex[:8].upper()}"
|
|
|
|
|
|
def _excerpt(value: str | None, *, max_chars: int = 2400) -> str:
|
|
if value is None:
|
|
value = ""
|
|
if len(value) <= max_chars:
|
|
return redacted(value)
|
|
return redacted(value[-max_chars:])
|
|
|
|
|
|
def _coerce_timeout_text(value: str | bytes | None) -> str:
|
|
if value is None:
|
|
return ""
|
|
if isinstance(value, bytes):
|
|
return value.decode("utf-8", errors="replace")
|
|
return value
|