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
- Open Control UI webchat session.
- Have a long-running session with significant cache reads (e.g.
cacheRead = 440k, inputTokens = 6 for the latest turn).
- Look at the status pill under the assistant bubble — it shows
0% ctx.
- 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
Bug: Control UI status bar
% ctxshows 0% even when server-side context is 44%Reporter: Giang Tran
Date: 2026-04-23
OpenClaw version:
2026.4.21(buildf788c88)Channel: Webchat (Control UI)
Summary
The Control UI message-status bar shows
0% ctxeven when the actual session context window is at 44% utilization (442k of 1.0m tokens forclaude-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
/statusslash command correctly reportsContext: 442k/1.0m (44%) · 🧹 Compactions: 0. So the server-side accounting is right; the bug is purely in the UI's local computation ofcontextPercent.Reproduction
cacheRead = 440k,inputTokens = 6for the latest turn).0% ctx./statusin the same session — it correctly showsContext: 442k/1.0m (44%).Root cause
In
dist/control-ui/assets/index-Bsj0vinf.js, the per-message metadata aggregator computescontextPercentfrominputTokensonly, ignoringcacheRead:When using prompt-cache aggressively (every modern Claude session does),
inputTokensper turn is just the small delta after cache hit (e.g. 6 tokens), whilecacheReadaccumulates the actual tokens consuming context budget (e.g. 440k).So:
6 / 1_000_000 * 100 ≈ 0.0006%→ rounded to0%(6 + 440_000) / 1_000_000 = 44%Comparison with
/statuscommand (which is correct)dist/status-message-NCClW_Lx.jscorrectly defines:Where
entry.totalTokensalready includes the full prompt cost (input + cacheRead + cacheWrite + output) accumulated over the session. This is what gets formatted into theContext: 442k/1.0m (44%)line.Suggested fix
In the UI aggregator, compute
contextPercentfrom total tokens consumed, not justinputTokens:…or, even simpler, surface the server-side
totalTokensfield directly in the per-message usage payload and let the UI use that without recomputing.Impact
A user who relies on this indicator may not run
/compactor/newin time, causing the next turn to hit the model's hard context limit and fail.Workaround
Run
/statusperiodically — it shows the trueContext: %andCompactions:count.Environment
2026.4.21buildf788c88v24.14.1anthropic/claude-opus-4-7(1M context), but reproduces with any cached model