Phase 1B — Statusline Input Contract¶
- Date:
2026-08-02 - Workstream: A (Claude Code boundary — statusline invocation and stdin payload)
Statusline command configuration¶
Verified fact: The active statusline is configured in ~/.claude/settings.json (user-level):
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/statusline.js",
"padding": 0
}
}
No statusLine override exists in the project settings (.claude/settings.local.json).
Current statusline script (~/.claude/statusline.js)¶
The script is a Node.js reader that parses the stdin JSON and renders:
| Display element | Source field |
|---|---|
| Model name | model.display_name \|\| model.id |
| Git branch | git symbolic-ref against cwd |
| Context usage % | context_window.used_percentage |
| Context window size | context_window.context_window_size |
| Token usage (last call) | context_window.current_usage.input_tokens / .output_tokens |
Verified fact: The script uses model.display_name as its primary model label. In this installation that field equals auto/cheap (the configured ANTHROPIC_MODEL), so the statusline currently displays auto/cheap — the requested model, not the resolved provider/model.
Observation: The script contains no fallback to read OmniRoute data, the transcript, or any external source. It renders only what Claude Code provides in the statusline payload.
Transcript reveals the resolved model¶
The most recent session transcript (abd68897-8f5d-47ca-96b5-815f75779afc.jsonl, 42,650 tokens) was inspected for model metadata (message content was not extracted).
Verified fact: Every type: "assistant" message in the transcript carries a model field containing the resolved model that actually handled the request, because OmniRoute returns it in the API response's model field. Observed values:
| Resolved model | Occurrences |
|---|---|
deepseek-v4-flash |
52 |
mimo-v2.5-free |
43 |
sonnet |
4 |
gemini-3-flash-preview |
2 |
Verified fact: The transcript also contains the value auto (7 occurrences), appearing in combo/step identifiers — not as a model response value. No standalone provider, resolved_provider, or display_name field exists in the transcript JSON structure; the resolved provider is embedded only in the model string returned by the API.
Verified fact: The display_name field does not appear anywhere in the transcript JSONL. Claude Code's statusline payload schema (model.display_name) is populated from a different source than the per-message API response model.
Key divergence: statusline payload vs transcript¶
| Source | Model label shown | What it represents |
|---|---|---|
Statusline payload (model.display_name) |
auto/cheap |
The requested model (what the client configured) |
Transcript (per-assistant-message model) |
deepseek-v4-flash, mimo-v2.5-free, etc. |
The resolved model (what OmniRoute actually routed to) |
Verified fact: This divergence exists because the statusline payload reflects the model Claude Code requests, while the transcript records the model OmniRoute returns in each API response. OmniRoute does echo the resolved model back in the response metadata — Claude Code stores it in the transcript, but the statusline contract does not surface it.
Observed vs documented schema¶
From the session transcript, the following documented statusline fields were observed or confirmed absent:
| Field | Status | Notes |
|---|---|---|
model.id |
Present | Set to auto/cheap |
model.display_name |
Present | Set to auto/cheap (same as id in this config) |
cwd |
Present | Working directory path |
workspace.current_dir |
Present | Same as cwd |
context_window.* |
Present | Populated after first API call |
cost.* |
Present | Session cost/duration |
session_id |
Present | UUID for current session |
transcript_path |
Present | Path to the JSONL transcript file |
version |
Present | Claude Code version |
effort.level |
Present (when active) | Current reasoning effort |
fast_mode |
Present | Boolean |
rate_limits.* |
Present (for subscribers) | 5h/7d usage |
session_name |
Conditional | Only after AI-generated title exists |
prompt_id |
Conditional | Only after first user input |
vim.mode |
Conditional | Only when vim mode enabled |
pr.* |
Conditional | Only when open PR exists |
worktree.* |
Conditional | Only during --worktree sessions |
agent.name |
Conditional | Only when --agent flag used |
| Any resolved-provider field | Absent | Not in documented schema; not in observed data |
Key findings¶
| Finding | Tag | Evidence |
|---|---|---|
The statusline payload carries the requested model (auto/cheap), not the resolved route |
Verified fact | model.display_name in the payload equals the configured ANTHROPIC_MODEL; script renders this value directly |
The transcript stores the resolved model per assistant message, because OmniRoute echoes it in the API response's model field |
Verified fact | 101 assistant messages carry resolved model names; no provider field exists alongside |
| There is no statusline payload field for resolved provider/model — this is a documented gap, not a bug | Verified fact | Full schema inspected; model.id/model.display_name are the only model fields |
A statusline can read the transcript's last assistant message model field to obtain the resolved model |
Observation | Transcript is JSONL at a known path (transcript_path); last line is low-latency to read; no auth required |
| The current statusline script does not use the transcript as a data source | Observation | Script source inspected; only reads stdin JSON payload |
Session-level correlation from OmniRoute call_logs is not functional (session_tag is NULL on all rows) |
Observation | SQLite inspection (Workstream B) — cross-referenced |
Implications for statusline design¶
Verified fact: Two viable read-only paths to the resolved model exist:
- SQLite path (via Docker volume):
call_logs.model+call_logs.provider— resolved per request, no auth needed, low-latency. Requires Docker permissions. - Transcript path (via JSONL): last assistant message
modelfield — resolved per message, no auth/Docker needed. Requires JSONL parsing.
Both are read-only and do not route live traffic. The transcript path is simpler (no Docker dependency) but provides only the model string, not the separate provider name. The SQLite path provides both model and provider separately.
Open questions¶
- Does the transcript
modelfield always reflect the resolved model, or only when the provider echoes it? (Likely depends on OmniRoute's response construction; current data confirms it for the providers observed.) - Can the transcript
modelfield reliably distinguish between providers (e.g.,deepseek-v4-flashvsmimo-v2.5-free), or is it sometimes the requested alias? - What is the latency of reading the last line of the transcript JSONL vs querying SQLite?