Skip to content

Control UI status bar shows '0% ctx' when actual session context is 44% — contextPercent ignores cacheRead #70491

Description

@giangthb

Bug: Control UI status bar % ctx shows 0% even when server-side context is 44%

Reporter: Giang Tran
Date: 2026-04-23
OpenClaw version: 2026.4.21 (build f788c88)
Channel: Webchat (Control UI)


Summary

The Control UI message-status bar shows 0% ctx even when the actual session context window is at 44% utilization (442k of 1.0m tokens for claude-opus-4-7). This is misleading — users think they have plenty of context headroom and don't compact in time, then hit unexpected limits.

The /status slash command correctly reports Context: 442k/1.0m (44%) · 🧹 Compactions: 0. So the server-side accounting is right; the bug is purely in the UI's local computation of contextPercent.


Reproduction

  1. Open Control UI webchat session.
  2. Have a long-running session with significant cache reads (e.g. cacheRead = 440k, inputTokens = 6 for the latest turn).
  3. Look at the status pill under the assistant bubble — it shows 0% ctx.
  4. Run /status in the same session — it correctly shows Context: 442k/1.0m (44%).

Root cause

In dist/control-ui/assets/index-Bsj0vinf.js, the per-message metadata aggregator computes contextPercent from inputTokens only, ignoring cacheRead:

let { message: t } of e.messages) {
  let e = t;
  if (e.role !== `assistant`) continue;
  let l = e.usage;
  l && (
    c = !0,
    n += l.input ?? l.inputTokens ?? 0,            // ← n = sum of inputs (per-turn delta after cache hit)
    r += l.output ?? l.outputTokens ?? 0,
    i += l.cacheRead ?? l.cache_read_input_tokens ?? 0,    // ← cacheRead computed but ignored
    a += l.cacheWrite ?? l.cache_creation_input_tokens ?? 0
  );
  // ...
}
// ↓ BUG: n is delta input tokens, not total context consumed
let l = t && n > 0 ? Math.min(Math.round(n / t * 100), 100) : null;
return { input: n, output: r, cacheRead: i, cacheWrite: a, cost: o, model: s, contextPercent: l };

When using prompt-cache aggressively (every modern Claude session does), inputTokens per turn is just the small delta after cache hit (e.g. 6 tokens), while cacheRead accumulates the actual tokens consuming context budget (e.g. 440k).

So:

  • UI computes: 6 / 1_000_000 * 100 ≈ 0.0006% → rounded to 0%
  • Reality: (6 + 440_000) / 1_000_000 = 44%

Comparison with /status command (which is correct)

dist/status-message-NCClW_Lx.js correctly defines:

let totalTokens = entry?.totalTokens ??
                  (entry?.inputTokens ?? 0) + (entry?.outputTokens ?? 0);

Where entry.totalTokens already includes the full prompt cost (input + cacheRead + cacheWrite + output) accumulated over the session. This is what gets formatted into the Context: 442k/1.0m (44%) line.


Suggested fix

In the UI aggregator, compute contextPercent from total tokens consumed, not just inputTokens:

- let l = t && n > 0 ? Math.min(Math.round(n / t * 100), 100) : null;
+ const totalTokens = n + i + a + r;   // input + cacheRead + cacheWrite + output
+ let l = t && totalTokens > 0 ? Math.min(Math.round(totalTokens / t * 100), 100) : null;

…or, even simpler, surface the server-side totalTokens field directly in the per-message usage payload and let the UI use that without recomputing.


Impact

  • Low severity functionally (no data loss, no crash)
  • High UX severity — silently misleads users about session health
  • Most affected: users running long agentic sessions with heavy prompt cache (typical Claude Opus or GPT-5.4 workflows)

A user who relies on this indicator may not run /compact or /new in time, causing the next turn to hit the model's hard context limit and fail.


Workaround

Run /status periodically — it shows the true Context: % and Compactions: count.


Environment

  • OpenClaw 2026.4.21 build f788c88
  • Node v24.14.1
  • Linux WSL2 6.6.87.2 on Windows 11
  • Browser: any (bug is in bundled UI JavaScript, browser-agnostic)
  • Model: anthropic/claude-opus-4-7 (1M context), but reproduces with any cached model

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions