ADR 0002: Selectable classifier CLI (Copilot or Claude Code)¶
Date: 2026-10-14
Status: Accepted
Decision scope: Which command-line tool runner.py uses for paid classification
Production default: unchanged ("cli": "copilot")
Decision¶
classifier.json selects the classifier CLI with "cli", either "copilot"
(GitHub Copilot CLI, the default) or "claude" (Claude Code). Each CLI keeps its
own binary and models under "providers", so switching is a one-key change and
neither CLI's settings are lost. The deterministic scanner, cache, validation,
task policy and reporting are shared and unchanged.
Claude Code defaults to the user's subscription login, with the pinned models
claude-haiku-5-5 (first pass) and claude-sonnet-5-5 (escalation).
Context¶
The runner already treated the model as an untrusted, isolated subprocess: no tools, a temporary working directory, mail on stdin, strict JSON validated locally. Some users have Claude subscriptions rather than Copilot seats, and Claude Code can be isolated to the same standard, so supporting it needs no new trust model.
Options considered¶
- Call the Anthropic API from Python with the SDK. Cleanest isolation and exact usage data, but it adds the project's first Python package dependency, needs an API key (no subscription login), and a key would have to be stored for the schedule. Rejected for now.
- Replace Copilot with Claude Code. Breaks existing installs and shared ZIPs. Rejected.
- A config switch over a small backend table (chosen). One
call_modelskeleton handles the temp directory, stdin, timeout, usage recording and error redaction; each backend supplies its command, environment checks, response parser and usage sanitizer. The older flat Copilot-only config is still read ascopilot.
Claude Code isolation¶
claude -p --output-format json --tools "" --strict-mcp-config --mcp-config
'{"mcpServers":{}}' --setting-sources project --disable-slash-commands
--no-session-persistence --permission-mode dontAsk --max-budget-usd <cap>
--system-prompt <instructions>, run in an empty temporary directory with mail on
stdin. The response must be one successful, tool-free turn.
Subscription vs --bare¶
--bare skips every automatic context source but accepts only
ANTHROPIC_API_KEY, which the schedule cannot hold safely. Synthetic probes on
Claude Code 2.1.284 with subscription auth and the flags above showed:
- the global
~/.claude/CLAUDE.mdand project instructions are not sent; - a short account section containing the signed-in email is sent;
CLAUDE_CODE_DISABLE_CLAUDE_MDS=1does not remove it and adds an environment section, so it is not used.
Sending the account email to the account's own provider was judged acceptable.
"auth": "api_key" remains available for interactive use and adds --bare;
schedule.py refuses to install with it.
Consequences¶
- Pinned model IDs. On Claude Code 2.1.284 the
haikualias resolves to Claude Haiku 4.5, so aliases would silently change classifications and cost. - CLI cost estimate is wrong for unrecognized IDs. That version does not
recognize
claude-haiku-5-5/claude-sonnet-5-5and prices them at Claude Opus 5.5 rates (about 40x Haiku). Usage keeps it only ascli_cost_usd; cost is computed from tokens, with 1-hour cache writes (cache_write_1h) priced separately.--max-budget-usduses the same estimate, so the default cap is $1.00, a runaway guard rather than a budget. - Small overhead. Replacing the system prompt keeps a call's fixed overhead near 700-800 cache-write tokens, against ~17,000 measured for Copilot.
- Refused proxies. A terminal inside a Claude Code session sets
ANTHROPIC_BASE_URL, which the runner refuses, so manual runs must come from a normal terminal. This is intended: mail must not be routed through an unexpected endpoint. - Skills in two places. Claude Code reads
.claude/skills/; the skills are byte-identical copies of.github/skills/, enforced by a test. - Cache invalidation on switch. The cache key includes the selected CLI and models, so switching re-classifies that day's pending mail once.
Open items¶
- Confirm that subscription auth works from the LaunchAgent with no terminal.
- Measure whether
--effort lowreduces Haiku 5.5's output tokens (696 output tokens for a two-message probe suggests default thinking) without lowering decision quality. - Revisit
--json-schemastructured output once a per-batch schema is designed.