Claude Code CLI (headless)
Default model: claude-sonnet-5-5 (CYBERINECLAUDEMODEL; was claude-sonnet-5 until 2026-10-01, kvhxs). Auth dir allow-listed by the sandbox: ~/.claude.
- Kind: headless coding agent (subprocess)
- Official docs: https://code.claude.com/docs/en/cli-reference · /headless · /authentication
- Cyberine adapter:
CliAgentAdapterviamakeClaudeAdapter(build workerclaude) - Researched/verified: 2026-07-13 — confidence high. Updated 2026-07-17 (T622/T624/T626/T628/T629):
makeClaudeCodeAdapter(the unreferenced--bareread-only variant) removed as dead code — grep confirmed nothing outside its own definition called it, andbuildAdapters.tshas nocase 'claude-code':.makeClaudeAdaptergained--exclude-dynamic-system-prompt-sections(unconditional on the default-prompt path), an opt-in--effortdial, opt-in--forward-subagent-textcapture, and opt-in--json-schemastructured output — see "Key flags" and "Cyberine env vars" below. Updated 2026-07-23 (T646, v2.1.212 headless deltas, live cross-checked against the installed v2.1.218 binary viaclaude --help+strings):CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION/CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION(new per-session caps, default 200 each) now have opt-in Cyberine overrides — see "Cyberine env vars". SIGTERM-in-print-mode now exits 143 cleanly (no orphaned tree); confirmedCliAgentAdapter's cancel path already classifies any exit following its own SIGTERM as'cancelled'regardless of the numeric code, so no code change was needed there. Transcripts now carry a reasoning-effort field; confirmedparseClaudeStreamJson/parseClaudeSessionTreepick named fields off each event and ignore unknown ones, so the new field is inert by construction.CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSis documented as a no-op today (noclaudebuild worker is ever given--mcp-config, the T488 MCP-bridge gap is still open). Updated 2026-08-20 (T656, live-verified against the installed claude v2.1.237): the T626/T628 opt-ins are now reachable viaCYBERINE_CLAUDE_CAPTURE_SUBAGENT_TEXT/CYBERINE_CLAUDE_OUTPUT_SCHEMA; unknown-flag rejections from an older claude (error: unknown option '--flag', exit 1) surface as named flag-compat errors (see integration notes). Updated 2026-08-27 (T627, live-probed and adopted against the installed v2.1.246):--no-session-persistenceis now applied on the cold-spawn path whenever!opts.warmSession && !opts.captureSessionTree-- see "Key flags" and "Gotchas" below for the probe evidence. Updated 2026-08-29 (T695, three headless deltas filed by the 2026-08-28 R02 sweep, cross-checked against the rawCHANGELOG.mdand live-verified against the installed v2.1.251 binary): all three confirmed harmless, no adapter change needed -- see "Gotchas" below for the full evidence chain (SIGTERM no longer recording an interrupted turn / synthetic tool denials, MCPheadersHelpernow requiring a trust-dialog accept, and sub-agent model-404 silently using the session's fallback chain).
Cyberine invocation
# build worker (makeClaudeAdapter), cold spawn
claude -p "<prompt>" --output-format stream-json --verbose \
--model <model> --dangerously-skip-permissions --safe-mode \
[--fallback-model <models>] [--thinking disabled] [--effort <level>] \
[--system-prompt <persona> | --exclude-dynamic-system-prompt-sections] \
[--forward-subagent-text] [--json-schema <schema>] \
[--no-session-persistence] --add-dir <cwd>
# build worker, warm-session resume (opt-in via opts.warmSession)
claude --resume <sessionId> -p "<prompt>" --output-format stream-json --verbose \
--dangerously-skip-permissions --safe-mode \
[--fallback-model <models>] [--thinking disabled] [--effort <level>] \
[--forward-subagent-text] [--json-schema <schema>] --add-dir <cwd>
# background fleet worker (makeClaudeBgAdapter, opt-in via CYBERINE_CLAUDE_WORKER_MODE=bg)
claude "<prompt>" --bg --name cyberine-w-<runId8>-<slot> --model <model> \
--permission-prompts none --dangerously-skip-permissions --safe-mode \
[--thinking disabled] [--effort <level>] [--system-prompt <persona>] \
--settings '{"env":{...}}' --add-dir <cwd>Default model: claude-sonnet-5-5 (CYBERINE_CLAUDE_MODEL; was claude-sonnet-5 until 2026-10-01, kvhxs). Auth dir allow-listed by the sandbox: ~/.claude.
--safe-mode and --dangerously-skip-permissions are applied unconditionally on every build-worker
spawn (cold or resumed). --fallback-model / --thinking disabled / --system-prompt are
conditional — see "Cyberine env vars" below.
Auth (precedence order)
- Cloud env:
CLAUDE_CODE_USE_BEDROCK=1/..._VERTEX=1/..._FOUNDRY=1 ANTHROPIC_AUTH_TOKEN→Authorization: Bearer(LLM gateways)ANTHROPIC_API_KEY→X-Api-Key(always used in-pmode when present)apiKeyHelpersetting (dynamic key script)CLAUDE_CODE_OAUTH_TOKEN(fromclaude setup-token, CI) — NOT read in--baremode- Subscription OAuth from
claude auth login(macOS Keychain /~/.claude/.credentials.json)
--baredoes not readCLAUDE_CODE_OAUTH_TOKEN→ useANTHROPIC_API_KEYorapiKeyHelperwith--bare.
Key flags
| Flag | Purpose |
|---|---|
-p / --print | Non-interactive (required for all headless flags). |
--output-format text|json|stream-json | stream-json = JSONL events (Cyberine uses this). |
--verbose | Required with stream-json to get intermediate events; else only the final result. |
--model <alias|id> | Aliases: sonnet, opus, haiku, best, opusplan, sonnet[1m], opus[1m]. |
--dangerously-skip-permissions | = --permission-mode bypassPermissions. Required headless; does NOT override deny rules. |
--safe-mode | Isolates the worker from the operator's global ~/.claude customizations (CLAUDE.md, skills, plugins, hooks, MCP, commands/agents, output styles, workflows, themes, keybindings); sets CLAUDE_CODE_SAFE_MODE=1. Does not affect auth, model selection, built-in tools, or permissions. Always passed by makeClaudeAdapter (T492). |
--fallback-model <alias|id>[,<alias|id>...] | Comma-separated fallback chain, tried on a primary-model failure; the CLI re-tries the primary at the start of each user turn. "Only works with --print" per claude --help. Opt-in via CYBERINE_CLAUDE_FALLBACK_MODEL (T494). |
--thinking <enabled|adaptive|disabled> | Help-hidden but real (commander option, v2.1.197). makeClaudeAdapter only ever passes disabled, opt-in via disableThinking / CYBERINE_CLAUDE_DISABLE_THINKING (T494) — the claude worker profile is tier strong, so thinking stays ON by default. |
--system-prompt <text> | Overrides the system prompt; Cyberine passes CYBERINE_SYSTEM_PROMPT only when config.persona === 'cyberine'. |
--exclude-dynamic-system-prompt-sections | Moves per-machine sections (cwd, env, memory paths, git status) out of the system prompt into the first user message, improving cross-user prompt-cache reuse. Only applies with the default system prompt (ignored with --system-prompt). Applied unconditionally by makeClaudeAdapter whenever config.persona !== 'cyberine' (T622). |
--effort <low|medium|high|xhigh|max> | Effort level for the session. Opt-in via opts.effort / CYBERINE_CLAUDE_EFFORT (T624); absent (default) = CC's own default for the model. |
--add-dir <path> | Extra file access; does not load .claude/ config from those dirs. |
--resume <sessionId> | Resumes a prior session (T333 warm-session reuse, opt-in via opts.warmSession); combined with -p for a follow-up headless turn. Falls back to a cold spawn when the resumed session exits non-zero. |
--bare | Skip hooks/skills/plugins/MCP/memory/CLAUDE.md (reproducible CI). Not currently used by any Cyberine factory (the former makeClaudeCodeAdapter read-only variant was removed as dead code 2026-07-17, T629). |
--include-partial-messages | Token-level text_delta events (needs -p + stream-json). |
--forward-subagent-text | Forwards subagent text/thinking as extra assistant/user messages tagged with parent_tool_use_id; only works with -p + --output-format=stream-json (always true here). Opt-in via CYBERINE_CLAUDE_CAPTURE_SUBAGENT_TEXT / opts.captureSubagentText (T626, wired T656); extracted into Result.raw.subagentText by parseClaudeSubagentText. |
--allowedTools <...> | Narrower alternative to skip-permissions (e.g. Bash(git diff *)). |
--max-turns <n> / --max-budget-usd <n> | Limits (print mode). |
--json-schema <schema> | JSON Schema text (not a file path, unlike Codex's --output-schema), passed straight through for structured-output validation. Validated JSON lands in the result event's structured_output field. Opt-in via CYBERINE_CLAUDE_OUTPUT_SCHEMA (inline JSON or a schema-file path, inlined to text in core/config.ts) / opts.outputSchema (T628, wired T656); extracted into Result.raw.structuredOutput by parseClaudeStructuredOutput. |
--no-session-persistence | "Disable session persistence - sessions will not be saved to disk and cannot be resumed (only works with --print)" per claude --help (verified on the installed v2.1.246). Applied by makeClaudeAdapter's cold-spawn baseArgs whenever !opts.warmSession && !opts.captureSessionTree (T627, adopted 2026-08-27) — a cold-spawn worker is never resumed, so the on-disk transcript is pure clutter; a warm-session or session-tree-capture worker keeps it unset. Live-probed before adoption: session_id and the system/init topology fields still appear in the live stream-json output with the flag set — see "Gotchas". |
claude auth login / setup-token / auth status | Auth management. |
Cyberine env vars
These control makeClaudeAdapter's own flag/env injection — distinct from the CLI's native auth env
vars in "Auth" above.
| Env var | Effect |
|---|---|
CYBERINE_CLAUDE_MODEL | Overrides the default model (claude-sonnet-5-5) passed via --model. |
CYBERINE_CLAUDE_FALLBACK_MODEL | Comma-separated model chain passed straight through to --fallback-model. Absent (default) = no fallback. |
CYBERINE_CLAUDE_DISABLE_THINKING | 1/true passes --thinking disabled. Defaults to unset (thinking stays on) — this worker's profile is tier strong. |
CYBERINE_CLAUDE_EFFORT | One of low/medium/high/xhigh/max, passed through to --effort. Validated in core/config.ts; an unrecognized value is treated as unset. Absent (default) = flag omitted, CC picks its own default. |
CYBERINE_CLAUDE_MAX_WEB_SEARCHES_PER_SESSION | T646: positive integer, translated into the CLI's own CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION env var (see table below). Validated in core/config.ts; a non-integer/non-positive value is treated as unset. Absent (default) = omitted, CC's own 200-per-session default applies unchanged. |
CYBERINE_CLAUDE_MAX_SUBAGENTS_PER_SESSION | T646: positive integer, translated into the CLI's own CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION env var (see table below). Same validation/default contract as the web-searches knob above. |
CYBERINE_CLAUDE_CAPTURE_SUBAGENT_TEXT | T626 (wired T656): 1/true passes --forward-subagent-text and extracts the tagged messages into Result.raw.subagentText. Absent (default) = off. |
CYBERINE_CLAUDE_OUTPUT_SCHEMA | T628 (wired T656): JSON Schema — inline JSON (starts with {) or a schema-file path (read and inlined in core/config.ts) — passed as --json-schema text; the validated object lands in Result.raw.structuredOutput. Unparseable value/file is treated as unset. |
Injected into the subprocess environment on every spawn (not CLI flags, not documented in
claude --help; verified present in the installed binary v2.1.197 via strings, re-confirmed
present in v2.1.218):
| Env var | Value | Purpose |
|---|---|---|
CLAUDE_CODE_DISABLE_BUNDLED_SKILLS | 1 | Belt-and-suspenders alongside --safe-mode: stops the spawned worker from resolving the operator's bundled/marketplace skills via /skill-name. |
CLAUDE_CODE_RETRY_WATCHDOG | 1 | Enables the CLI's own retry watchdog so a stalled/hung request under this unattended headless spawn gets retried instead of hanging until the adapter's own timeoutMs kills the process. |
CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION | opt-in, from CYBERINE_CLAUDE_MAX_WEB_SEARCHES_PER_SESSION | T646 (v2.1.212 new cap, default 200 — verified via strings on the v2.1.218 binary). Only injected when the Cyberine knob is set; omitted (default) leaves CC's own 200-per-session default in force. |
CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION | opt-in, from CYBERINE_CLAUDE_MAX_SUBAGENTS_PER_SESSION | T646 (v2.1.212 new cap, default 200 — verified via strings on the v2.1.218 binary). Same opt-in contract as the web-searches cap above. |
Models / aliases
claude-sonnet-5-5 (sonnet, Cyberine default since 2026-10-01; CC v2.1.284 made it the default Sonnet), claude-sonnet-5 (previous sonnet, legacy), claude-opus-5 ("now the default Opus model" per CC v2.1.219 release note, 2026-07-24 -- not independently verified against opus alias resolution on the installed binary), claude-opus-4-8 (previous opus; needs CC v2.1.154+), claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-5, claude-fable-5-1 (CC v2.1.257, 2026-09-04 sweep -- 1M ctx, $10/$50 with a $0.025x cache-hit rate; NOT yet in Cyberine's catalog/pricing, see the T708 sweep note below). Aliases: sonnet/opus/haiku/best/opusplan/sonnet[1m]/opus[1m]; fable and best resolve to Fable 5 on gateways not yet configured (CC v2.1.257).
stream-json event schema
- Final
resultevent (what Cyberine parses —parseClaudeStreamJson):{type:'result', subtype:'success', result:<string>, session_id, duration_ms, num_turns, total_cost_usd, usage:{input_tokens, output_tokens}, modelUsage:{...}, terminal_reason}. - Error subtypes:
error_max_turns/error_during_execution/error_max_budget_usd/error_max_structured_output_retries(noresult;errors[]instead). v2.1.272 addedturn_preempted. Since kq5rd (2026-09-16)parseClaudeStreamJsonfails the run on ANY present subtype other thansuccess, naming it, so a new subtype cannot land as an empty success. - Intermediate (need
--verbose):system/init(first; session/model/tools/plugins),assistant,tool_use,tool_result,system/api_retry. - Token deltas (need
--include-partial-messages): nested.event.delta.type=='text_delta'. - Session topology (T333,
parseClaudeSessionTree):system/initevents carrysession_id+ optionalparent_session_id; a session with no parent is the root. rate_limit_event(kxvc9, live-probed 2026-09-06 on v2.1.263) — its OWN top-level line, one per turn, immediately before the finalresult, present on an ordinary-pcall and not only when limited. Probe:claude -p "Reply with exactly: ok" --model haiku --output-format stream-json --verbose --permission-prompts none | grep rate_limit_event. Shape:{type:'rate_limit_event', rate_limit_info:{status:'allowed'|'allowed_warning'|'rejected', resetsAt:<epoch SECONDS>, rateLimitType:'five_hour'|'seven_day'|..., unifiedWindows:{five_hour:{utilization,resetsAt}, seven_day:{utilization,resetsAt}}, overageStatus, overageDisabledReason, isUsingOverage}, uuid, session_id}.utilizationis a 0..1 fraction per window; the 5-hour and 7-day windows are SEPARATE numbers. There is no headless "remaining usage" endpoint (/usageis interactive-only, noclaude usagesubcommand, verified on v2.1.261-263): this event is the only readout, andparseClaudeRateLimitEvent(src/adapters/claudeStreamJson.ts) reads the LAST one per turn, normalising a millisecondresetsAtto seconds. UNVERIFIED (kp5x7, 2026-09-06): whether a window that runs out MID-turn ends the turn withis_error: trueand arejectedevent, or only on the next turn. Cyberine's autopilot classifies both shapes (event status, or result text matching the rate-limit vocabulary) so either behaviour pauses the loop; record the observed shape here the first time a real run hits it.
Gotchas
- stdin-closed subprocesses stall forever on the first permission prompt without
--dangerously-skip-permissions(no timeout fallback). --add-dirgrants file access only —.claude/config is NOT loaded from those dirs.--bareignoresCLAUDE_CODE_OAUTH_TOKEN.ANTHROPIC_API_KEYtakes precedence over subscription OAuth;unsetit to fall back.--verboserequired for intermediate events; Cyberine parses the lasttype:'result'line.Cache token sub-fields may appear inCORRECTED 2026-09-06 (keep5), live-probed on the installed v2.1.261.modelUsageentries but aren't documented top-levelusagefields — don't assume.result.usageDOES carrycache_read_input_tokensandcache_creation_input_tokensat the top level, alongsideinput_tokens/output_tokens, plusoutput_tokens_details,cache_creation(ephemeral_5m_input_tokens/ephemeral_1h_input_tokens),iterations[],service_tierandserver_tool_use. Captured fromclaude -p "Reply with exactly: probe-ok" --output-format stream-json --verbose --permission-prompts none --model haiku. This matters because the SIZE OF THE CONTEXT isinput_tokens + cache_read_input_tokens + cache_creation_input_tokens— on a warm resumed sessioninput_tokensis a literal handful (measured: 2, against a real context of 67,410) while the rest arrives ascache_read_input_tokens. Anything readinginput_tokensalone is not approximating the context, it is reporting a different quantity.src/adapters/claudeStreamJson.ts(readClaudeTurnTokens) is the one reader; it still defaults every field to 0, so a binary that omits them degrades to a smaller number rather than a crash. CORRECTED AGAIN 2026-09-06 (run 0dec102d), live-probed on v2.1.261 with a two-call turn.result.usageis the turn TOTAL: the CLI sums every model call it made (one per tool round). Probe (claude -p "Run echo ... then reply probe-ok" --allowedTools Bash --model haiku): call 1 usage{input 10, cache_read 12166, cache_creation 22343}, call 2{8, 34509, 259}, andresult.usage={18, 46675, 22602}— the field-wise sum, 69,295 "context" for a session whose real context was 34,776. On a 114-call turn the same sum read 9,864,289 tokens for a 195,653-token session and the autopilot's fill policy saw 986%. The context of a turn is the prompt of its LAST call, and each top-levelassistantevent carries that call's ownmessage.usage(one event per content block, identical usage, samemessage.id; subagent events carryparent_tool_use_idand their own context).readClaudeContextTokens(stdout, resultEvent)is the reader for that; thereadClaudeTurnTokenstotals stay correct as TOTALS (cost attribution, per-turn accounting).result.modelUsageis keyed by the RESOLVED model id (probed:claude-haiku-4-5-20251001), which is how a caller learns which model actually answered — a bareclaude -pwith no--modelinherits whatever/modelthe operator last picked interactively.- From 2026-06-15,
claude -p/ Agent SDK usage on subscription plans draws a separate monthly Agent SDK credit. - Background Bash tasks are killed ~5s after the final result (v2.1.163).
- kvks8, live-probed 2026-09-18 on v2.1.276 — do not re-probe blind. The Monitor tool's
10-minute hard deadline in
-pmode (v2.1.271+) does NOT stall a stdin-closed headless worker: it fires atask_notification {"status":"stopped"}with no user input required, the model re-arms Monitor unprompted in its very next turn, and the real background task's own completion arrives via a SEPARATE, always-ontask_notification {"status":"completed"}channel that is not gated on Monitor being currently armed — so even a re-armed Monitor that never itself detects an already-finished task just idles out its own new deadline as one harmless extra turn, it never blocks the real result. Measured: a worker told to background a 12-minute command and Monitor-wait for it reachedresult/exit 0at 15.3 min wall time (12.1 min real work + ~3.2 min overhead from the deadline-then-redundant-expiry pattern), comfortably inside the adapter's default 30-minutetimeoutMs(config.openCodeTimeoutMs/CYBERINE_CLI_TIMEOUT_MS). No adapter change made — see.local/research/2026-09-18-kvks8-monitor-deadline.m3max.mdfor the full event timeline. --safe-modeisolates config/customizations but not auth, model selection, or permissions — the explicit--dangerously-skip-permissionsand--system-promptoverrides still apply on top of it.- v2.1.212+: SIGTERM in print/SDK mode now exits 143 cleanly (no orphaned process tree) —
CliAgentAdapternever inspects the exit code on its own SIGTERM-driven cancel path (it short-circuits on an internalabortedflag first), so this needed no adapter change (T646). - v2.1.212+: new per-session caps
CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION/CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION(default 200 each) can silently truncate a research-heavy or internally-fan-out-heavy singleclaudesession. Override viaCYBERINE_CLAUDE_MAX_WEB_SEARCHES_PER_SESSION/CYBERINE_CLAUDE_MAX_SUBAGENTS_PER_SESSION(T646) — absent (default) leaves CC's own 200 in force. - T627, GO decision, recorded 2026-08-27 — do not re-probe blind. Installed CLI:
2.1.246 (Claude Code). Probe 1:claude -p --output-format json --no-session-persistence "reply with exactly: probe-ok"— the final line still carried"session_id":"a9e917d2-..."(plus a full result payload); no file matching that session id was ever written under~/.claude/projects(checked viafind), confirming the flag suppresses only the on-disk transcript, not the reported id. Probe 2 (cheap model, the real shape this adapter uses):claude -p --model haiku --output-format stream-json --verbose --no-session-persistence "reply with exactly: probe-ok"— thesystem/initevent still carried"session_id":"22207914-..."(noparent_session_id, correctly a root session) and the finalresultevent repeated the samesession_id; again no transcript file was written for that id. Those two fields are exactly whatparseClaudeSessionTree(T333) reads, so session-topology capture is unaffected by the flag in principle —makeClaudeAdapterstill omits the flag wheneveropts.captureSessionTreeis set, kept conservative on purpose (see the inline comment at the flag's call site insrc/adapters/CliAgentAdapter.ts). Decision: adopted on the cold-spawn path, scoped!opts.warmSession && !opts.captureSessionTree; regression guardnpm run selftest:no-session-persistence(scripts/t627-no-session-persistence-smoke.ts). - T695, three CONFIRMED-HARMLESS decisions, recorded 2026-08-29 — do not re-probe blind.
Filed by the 2026-08-28 R02 sweep against CHANGELOG-WATCH's reviewed high-water mark
v2.1.247; live-checked against the installed v2.1.251 binary (
claude --version) and the rawanthropics/claude-codeCHANGELOG.md(WebFetch, 2026-08-29). Version correction: the R02 sweep's note attributed the SIGTERM delta to v2.1.247 and the MCPheadersHelperdeltas to v2.1.238+v2.1.239 — the raw changelog places the SIGTERM bullet at v2.1.234 (verbatim match otherwise) and all fourheadersHelper/trust-dialog bullets at v2.1.238 alone (no v2.1.239 hit); the sub-agent model-404 fix is correctly v2.1.247. Immaterial to the verdicts below since the installed binary (v2.1.251) is past all of them either way.- SIGTERM in print/SDK mode no longer records an interrupted turn or synthetic tool
denials before exiting (v2.1.234). Harmless:
parseClaudeStreamJsononly ever reads the finaltype:'result'stream-json line and never branches on subtype or looks for interrupted/denial markers, so it never depended on the removed records; theCliAgentAdaptertimeout/cancel classification (aborted/timedOutflags) never inspects transcript content either (T646 already established this for the exit-code side). Doubly moot for the on-disk transcript reading of the same changelog line (relevant to a future resume):runStreaming's session cache never stores a session ID for warm reuse whenresult.status === 'error' || result.status === 'cancelled'(see the two guards around lines ~712 and ~738 insrc/adapters/CliAgentAdapter.ts), so aclaudeworker killed by timeout or by cancel is never subsequently resumed by Cyberine — whatever that session's persisted transcript does or doesn't record about the interrupted turn is never read back. No code change; no new smoke. - MCP
headersHelperin a project.mcp.json, and inline MCP servers in project or--add-diragent files, now require that folder's trust dialog to have been accepted (also underclaude -p) (v2.1.238). Harmless, doubly so: (a) the T488 MCP-bridge gap is still open — noclaudebuild worker is ever spawned with--mcp-config, so Cyberine never wires an MCP server into a worker's argv in the first place; (b)makeClaudeAdapterapplies--safe-modeunconditionally on every build-worker spawn (cold or resumed), andclaude --helpdescribes it as disabling "all customizations (CLAUDE.md, skills, plugins, hooks, MCP servers, custom commands and agents, ...)" — so even this repo's own committed root.mcp.json(a plaincyberine-mcpcommand entry, noheadersHelper) is inert for a spawned worker regardless of the trust-dialog change. No code change; no new smoke. - Fixed sub-agents dying on a first-call model 404: they now use the session's fallback
model chain (v2.1.247). Harmless: this fix is scoped to Claude Code's own internal
Task-tool sub-agents inside a session, not the top-level process
CliAgentAdapterspawns or its--model/--fallback-modelselection. More importantly, Cyberine never reads which model actually served a request from the CLI's own output —Result.provider(claude:${model}) is set once, statically, from the configuredmodelat adapter construction (makeClaudeAdapter,src/adapters/CliAgentAdapter.ts) and propagated unchanged intoWorkerRun.providerviaagent.info.provider(src/orchestrator/orchestrateAsync.ts);parseClaudeStreamJsonextracts onlyresult/usage/total_cost_usd/opt-in extras, never a model id, from the stream-json events. So a silent internal fallback (sub-agent-scoped, or even a top-level one under an opted-in--fallback-modelchain — already-existing, pre-dating this fix) has no field to corrupt: the recorded provider/model was always "what was requested," never "what actually served," before or after this change. No code change; no new smoke.
- SIGTERM in print/SDK mode no longer records an interrupted turn or synthetic tool
denials before exiting (v2.1.234). Harmless:
2026-09-04 sweep (T708): v2.1.248 -> v2.1.260, each version dispositioned
The row's baseline said v2.1.247 while the installed binary reported v2.1.260 -- wider than the
gap T708 was filed for (it named 2.1.248-2.1.251, measured when the binary was 2.1.251). Diffed the
raw anthropics/claude-code CHANGELOG.md for all 13 releases against the surface Cyberine actually
drives (claude -p, --output-format stream-json --verbose, --safe-mode,
--dangerously-skip-permissions, --no-session-persistence, --fallback-model, --model,
--forward-subagent-text, --exclude-dynamic-system-prompt-sections, session resume, SIGTERM).
2.1.253-2.1.256 have no changelog entries; 2.1.250 is "Bug fixes and reliability improvements".
Both filed tasks are now CLOSED — see the two verdict blocks below (k7hha) and the catalog
row (kmq47, claude-fable-5-1 added to modelCatalog.ts + modelPricing.ts).
k7hha verdicts: --permission-prompts none ADOPTED, --restricted DECLINED
--permission-prompts <host|none> (v2.1.259) — ADOPTED, unconditional on every claude spawn.
Verbatim from claude --help on the installed v2.1.260: "Who answers permission prompts with
--print: 'host' (the SDK host or --permission-prompt-tool) or 'none' (nobody: anything that would
prompt is denied automatically; the permission mode still decides everything else) (choices:
'host', 'none', default: 'host')". The DEFAULT is the defect for this adapter: a worker is spawned
with stdin closed and with neither an SDK host nor --permission-prompt-tool, so under host a
prompt goes to an answerer that does not exist and can only stall to the adapter's own timeout with
no exit code. --dangerously-skip-permissions covers only what the permission MODE decides — a
folder trust dialog (T695 found the v2.1.238 MCP headersHelper change can raise one under
claude -p, in a throwaway worktree no operator has ever trusted) or an MCP elicitation is not
that. Deny-and-fail-fast beats a 30-minute hang. Live-probed before adoption (T627's precedent),
v2.1.260, --output-format json alongside --dangerously-skip-permissions --safe-mode: a plain
turn returned is_error: false, subtype: "success", result: "OK", and a TOOL-USING turn
(write a file) returned is_error: false, permission_denials: [], num_turns: 2 with the file
actually written — so the flag does not turn skip-permissions' auto-allow into an auto-deny. No
disable knob on purpose: an operator who wants the old behaviour wants a hang. Registered in
CLI_FLAG_COMPAT (knownGoodVersion: 2.1.259) so an older binary gets the named T656 diagnosis.
Guard: npm run selftest:permission-prompts.
--restricted (v2.1.248) — DECLINED, no posture to apply it to. Verbatim: "Restricted mode:
removes the built-in tools that run commands or code (Bash, PowerShell, REPL and the other
code-running tools) and WebFetch unless --tools names them, and ignores user, project and local
settings files (managed settings and --settings still ...)". Wrong for a BUILD worker by
construction — it removes exactly the tools the worker exists to use — and there is no read-only
claude posture left to give it to: makeClaudeCodeAdapter, the --bare read-only variant, was
deleted as dead code by T622 (nothing called it and buildAdapters.ts had no case 'claude-code':).
Its settings-ignoring half is already covered for every spawn by --safe-mode. Re-evaluate only if
a read-only claude worker is reintroduced; adopting it now would mean inventing the caller first.
MATERIAL, task filed:
- v2.1.257: new model
claude-fable-5-1. Verbatim: "Added Claude Fable 5.1 (claude-fable-5-1), 1M context, $10/$50 per Mtok with $0.25/Mtok cache". Cyberine's catalog and pricing carry onlyclaude-fable-5(modelCatalog.ts,modelPricing.ts) -- the same gap class k3kbw closed forclaude-opus-5. Cross-checked against the live pricing page rather than trusting the release note (the k3kbw lesson: that note's$10/$50for opus-5 was the FAST-MODE rate, not standard). This time the note is right, and the page adds a detail the note compresses: base input $10 / MTok, output $50 / MTok -- identical to Fable 5 -- but the CACHE-HIT rate is $0.25 / MTok, because "Cache hits and refreshes on Claude Fable 5.1 and Claude Mythos 5.1 are priced at 0.025x the base input price. All other models use the standard 0.1x multiplier." Fable 5 charges $1/MTok for the same read. 5m cache write $12.50, 1h $20, batch $5/$25. Filed. - v2.1.248
--restrictedand v2.1.259--permission-prompts none, both new flags on the headless path: "Added--restrictedremoving built-in command/code tools andWebFetch" and "Added--permission-prompts nonefor unattended headless hosts to deny anything prompting". Neither is adoptable for a BUILD worker (which needs exactly the tools--restrictedremoves, and already runs--dangerously-skip-permissions), but both are candidate postures for a read-only/research worker and for the belt-and-braces case where a permission prompt would otherwise stall a stdin-closed spawn. Filed as one evaluation task, not implemented inline.
HARMLESS, recorded with evidence, no code change:
- v2.1.251: "Changed default commit trailer to
Co-Authored-By: Claude Code". This repo's global git rule bans that trailer outright, so the change is worth naming -- but it cannot reach a Cyberine commit: every commit Cyberine makes is made by Cyberine's own code (swarmLaneRunner.ts'sgit commit -q,kanbanRunner.ts's auto-commit) with an explicit message, andcaptureDiffreadsgit diff --cachedafter its owngit add -A, so the winner path never depends on a worker committing. The residual exposure is aclaudeworker that decides to commit on its own mid-task; the trailer would then land in that worktree commit, which is discarded rather than merged (the diff is what ships). Recorded, not guarded. - v2.1.251: "Changed
CLAUDE_CODE_SUBAGENT_MODELsetting default over agent definition", plus v2.1.257's "AddedCLAUDE_CODE_SUBAGENT_MODEL_FORCEto apply model to every subagent". Cyberine never sets either var for a worker (they are absent frommakeClaudeAdapter'senvSetand fromCliAgentAdapter's team-var re-add list), but a trusted spawn inherits the operator's environment -- so an operator who exportsCLAUDE_CODE_SUBAGENT_MODEL=opusnow has that win over a worker's own agent definitions where the definition used to win. A cost note, not a correctness one: it changes which model a claude worker's INTERNAL subagents bill to, and Cyberine reads cost from the CLI's owntotal_cost_usd, so the number stays truthful either way. - v2.1.257 + v2.1.259: three worktree/sandbox FIXES land on the side Cyberine is on. Verbatim:
"Fixed worktree-isolated sessions refusing complex Bash,
$VARreads, heredocs", "Fixed worktree-isolated sessions refusing common Bash loops and xargs pipelines", "Fixed sandboxed git in linked worktree losing write access aftercdto subdirectory". Every isolate step spawns its worker inside a detachedgit worktree(worktreeSandbox.ts) under the host sandbox, so these are exactly the shapes a build worker runs -- the installed 2.1.260 is the fixed side, and no Cyberine change is needed to benefit. - v2.1.260: "Fixed
-p --resume/--continuefailing on retry when worktree directory lost git metadata". Same class, same direction: the warm-session resume path (opts.warmSession) drives-p --resumeinside a worktree. - v2.1.258: "Fixed Claude Code failing to launch on macOS 12 (Monterey), regression from
2.1.255". Informational -- this machine is on Darwin 25, but it names a two-release window in
which a
claudeworker would not have started at all on an older macOS. - v2.1.260's
Reverted 2.1.259 change applyingRead()deny rules to Bash arguments, and every other entry across the 13 releases: interactive TUI, VSCode, Remote Control, Desktop, gateway, managed-settings, plugin,/slash-command and agent-team surfaces. None touches the print-mode spawn, the stream-json shapeparseClaudeStreamJsonreads, or a flag Cyberine passes.
kz74p: --bg refuses an untrusted cwd when not interactive (CC 2.1.281+) — READ FROM THE 2.1.282 BINARY 2026-09-25
Changelog 2.1.281: "Fixed claude --bg starting a background session, and running its project hooks, in a directory that had not passed the workspace trust prompt; it now asks for trust first, or exits when not run interactively". Read from the installed 2.1.282 binary's bundled source (not a live spawn, which would bill a turn and register a fleet row if the check passed):
- The
--bgdispatch runs a trust check on the cwd before anything else. Its verdicts:sandboxedwhenCLAUDE_CODE_SANDBOXEDis set,persistedwhen the cwd has accepted trust or sits under a root that has (the code consults the git root and the launching cwd as candidate roots; the exact walk was not traced, so a Cyberine worktree under<repo>/.claude/worktrees/inheriting the repo's trust is likely but unconfirmed),sessionfor the home directory on an interactive terminal, elseuntrusted. - When stdin and stdout are both a TTY it shows the trust prompt. Otherwise an
untrustedverdict writes to stderr and sets exit code1, with no job id on stdout:Workspace not trusted. Run `claude` in <dir> once and accept the trust prompt, then retry.(an unresolvable path getsWorkspace not trusted. <dir> could not be resolved on disk.; the home directory gets a home-specific line).--dangerously-skip-permissionsand--permission-prompts nonedo not bypass it. claude respawnon an untrusted job fails the same way witherrorCode: workspace_untrusted.
Cyberine's side: createBgSessionDriver (every claude --bg spawn, including ClaudeBgAdapter) maps a non-zero exit whose stderr matches Workspace not trusted. to BgSessionDriverError code untrusted-cwd, naming the cwd and the fix, instead of the generic spawn-failed. It does not pre-trust a directory: writing trust into ~/.claude.json on the operator's behalf would defeat the point of the prompt. Pinned by the kz74p case in npm run selftest:autopilot-bg-driver (fake CLI printing the real line).
k56eq: a --bg session job is NOT a child of the client — MEASURED 2026-09-08, do not re-probe blind
Installed CLI 2.1.263. A claude "<prompt>" --bg --name cyberine-w-probe1-s1 --model haiku --permission-prompts none --dangerously-skip-permissions client was spawned from a Node process
whose environment had CYBERINE_BG_PROBE_MARKER=present ADDED and SHELL DELETED. The job wrote
CYBERINE_BG_PROBE_MARKER=unset, SHELL=SET, HOME=SET, CYBERINE_TEAM=unset (presence tests
only, no values). Both halves are load-bearing: the added marker never arrived, and the deleted
SHELL still did. The worker runs under the daemon with the daemon's own recorded environment;
nothing Cyberine puts in the client's spawn env reaches it, and nothing it removes is removed. The
client exited 0 while the job ran on. This extends kwx2r's --exec measurement to the --bg
SESSION form. Three consequences, all now enforced in src/adapters/ClaudeBgAdapter.ts:
hostSandbox.tscannot confine a bg worker.wrapSpawnwould wrap the short-lived client only. The bg adapter never wraps, reportsResult.raw.sandbox: 'none', and REFUSES withHostSandboxErrorwhenCYBERINE_TASK_TRUSTED=false.envScrubcannot be applied, so it is enforced by refusal: a metered key present in Cyberine's own env blocks the spawn (the error names the key, never the value) unlessCYBERINE_CLAUDE_BG_ALLOW_METERED_ENV=1.--settings '{"env":{...}}'DOES deliver environment to a daemon-spawned job. Live-verified with a control on the same CLI: a job spawned with--settings '{"env":{"CLAUDE_CODE_DISABLE_BUNDLED_SKILLS":"1","CLAUDE_CODE_RETRY_WATCHDOG":"1"}}'reported both asSET, whileCYBERINE_K56EQ_CONTROL(in neither--settingsnor the client env) reportedunset. Only SETTING is proven; whether an empty value can NEUTRALISE an inherited variable was NOT measured, which is why the refusal above is not replaced by an empty-string trick. A second--settingsREPLACES the first, so theenvblock and kqq2g'sbashOutputMaxCharsshare one JSON object.
The same job's transcript (~/.claude/projects/<realpath cwd slug>/<sessionId>.jsonl) carried the
last top-level assistant message.usage
({input_tokens, cache_read_input_tokens, cache_creation_input_tokens, output_tokens} plus
output_tokens_details / cache_creation / iterations / service_tier), one
system/turn_duration, and neither total_cost_usd nor rate_limit_event anywhere in the
file — so a bg worker has no cost and no live quota signal, by construction rather than by
assumption. Guard: npm run selftest:bg-fleet-worker.
Background-job (claude agents) state vocabulary — MEASURED 2026-09-08 on 2.1.263
claude agents --json --all rows carry {id, cwd, kind, name, sessionId, startedAt, state} — no usage,
no exit code. state is one of starting | working | blocked | idle | done | stopped |
failed (the seven literals present in the 2.1.263 binary; a live fleet of 37 showed done 20,
blocked 16, working 1, and null for the 3 kind: interactive rows, which have no daemon state).
The row's private ~/.claude/jobs/<id>/state.json (read-only for us) adds tempo (active | blocked
| idle), needs (free text: the prompt the session is waiting for, e.g. send your next prompt to the successor agent), detail (last output line), suggestedReply, inFlight, template (bg | exec),
respawnFlags, firstTerminalAt / lastTerminalAt. The TUI label is DERIVED: blocked + a needs
text renders Needs input; blocked with needs: null is a turn that ended and awaits the operator
(the same row rendered Failed for a minute right after claude respawn --all replaced its process);
done renders Done; failed renders Failed with the exit code in detail (an exec row killed by
SIGTERM lands here as exit 143); stopped after claude stop; starting between dispatch and the first
output. A blocked row is not an error — for a Claude session it is the normal between-turns state.
An exec row (template: exec, pid: null) is never blocked / Needs input: the daemon derives its
working / idle from PTY output activity (~30 s of silence -> idle). Cyberine readers:
src/autopilot/bgSessionDriver.ts (BG_IDLE_STATES, turn-boundary detection), src/adapters/ClaudeBgAdapter.ts.
Pricing
Subscription (Pro/Max/Team/Enterprise) or API pay-as-you-go. From 2026-06-15, headless usage on subscriptions draws a separate Agent SDK credit. https://claude.com/pricing
Cyberine integration notes
makeClaudeAdapter(build workerclaude) gated on install + headless login (cliAvailability.ts); spawned under the host sandbox with~/.claudewritable.- Flag-compat diagnosis (T656): an older installed
claudethat rejects a version-gated flag (commander emitserror: unknown option '--flag', exit 1) surfaces a named error carrying the flag, the owning task, a known-good version, and the disable knob — registryCLI_FLAG_COMPATinsrc/adapters/CliAgentAdapter.ts, smokenpm run selftest:cli-flag-compat. - Underlying API: anthropic.md.
Sources
CLI Coding Agents — adapter integration reference
Pre-crawled, fact-checked reference for adding headless CLI coding agents as Cyberine workers (CliAgentAdapter spawns the official binary non-interactively, parses stdout into a Result)....
OpenAI Codex CLI (headless)
Model: CYBERINECODEXMODEL if set, else Codex's configured default (no -m added). Deliberately unpinned (kd7ax, 2026-09-09): Codex 0.153.4 made GPT-6-Astra the bundled default, and Cyberine keeps...