Caching and Timeout Policy¶
- Date: 2026-08-02
- Status: Approved (2026-08-02, ADR-0009)
Execution model¶
Claude Code spawns the statusline as a short-lived process per event, debounced to ~300 ms. The design is therefore stateless across invocations: no daemon, no persistence, no cross-invocation cache. Each invocation re-reads its inputs within the budget.
Reads per invocation and their budgets¶
| Read | Budget | Measured latency | Verdict |
|---|---|---|---|
| Transcript tail (bounded read; window size is an implementation constant, currently 256 KB) | 250 ms | sub-ms | Always within budget; no cache needed |
Mapping file (~/.claude/statusline-model-provider.json) |
50 ms | sub-ms (small JSON) | Always within budget; read once per invocation |
SQLite enrichment (docker run + sqlite query) — optional, off by default |
250 ms | 0.21–0.29 s startup; 0.73–1.13 s with apk add; pre-baked image ~0.25 s (inferred) |
Marginal at best; the reason this path is not the default |
Hard timeouts: every read has a hard timeout (above). On timeout, the design falls back: transcript timeout → requested label; mapping timeout → provider omitted; SQLite timeout → model-only.
Cancellation: Claude Code cancels over-budget scripts; nothing renders. The default path (transcript + mapping) completes in milliseconds, so cancellation never triggers in the default configuration.
Why the empirical mapping is the default provider source¶
Two verified constraints push the mapping ahead of SQLite enrichment:
- Latency. The statusline's 300 ms debounce is a budget, not a hint. A
docker runread is 0.21–0.29 s of container startup alone; withapk add sqlite3(no sqlite3 exists in theomnirouteoralpineimages today) it is 0.73–1.13 s — over budget. A pre-baked sqlite3 image (~0.25 s) is borderline and does not exist yet; building one is a Phase 4+ artifact, not a design assumption. - Classifier gating. The auto/cheap classifier gates Bash operations (observed in Phase 1B and again in this session) and the
docker runread path was gated in Phase 1B. The statusline must not depend on non-deterministic gating — the architecture requires a statically-allowlisted read path. A plain file read (transcript, mapping) is not classifier-gated; adocker runinvocation may be.
The mapping is fast, local, free of Docker and classifier dependencies, and satisfies the provider-aware objective for the observed catalog. It is a heuristic (ADR-0006) and degrades to model-only on a miss.
SQLite enrichment (documented optional path, not the default)¶
If a later phase enables provider enrichment from call_logs:
- Requires a pre-baked sqlite3 reader image (never
apk addat read time) and Docker running. - Runs with the 250 ms hard timeout and must tolerate cancellation.
- Correlates the last assistant-message timestamp to
call_logs.timestamp(verified: 68 ms apart in the observed sample), filteringcombo_namenon-empty,requested_model != 'connection-test',status = 200. - Falls back to model-only on no match, timeout, Docker-down, or classifier gating.
- This is deliberately NOT enabled in the first implementation; the mapping covers the objective within budget.
Staleness (the one "cache" concern)¶
The transcript records the last assistant message's timestamp. The staleness check compares it against the wall clock at invocation time; no state is stored between invocations. Threshold: 5 minutes (01-display-requirements.md).
Policy summary¶
- Stateless per invocation; never write anything.
- Read only within the per-read budget; fall back, never block, never crash.
- Default provider source is the local mapping; SQLite enrichment stays off until a pre-baked image exists.
- Tolerate cancellation; complete in milliseconds on the default path.