Skip to content

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:

  1. Latency. The statusline's 300 ms debounce is a budget, not a hint. A docker run read is 0.21–0.29 s of container startup alone; with apk add sqlite3 (no sqlite3 exists in the omniroute or alpine images 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.
  2. Classifier gating. The auto/cheap classifier gates Bash operations (observed in Phase 1B and again in this session) and the docker run read 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; a docker run invocation 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 add at 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), filtering combo_name non-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

  1. Stateless per invocation; never write anything.
  2. Read only within the per-read budget; fall back, never block, never crash.
  3. Default provider source is the local mapping; SQLite enrichment stays off until a pre-baked image exists.
  4. Tolerate cancellation; complete in milliseconds on the default path.