fix: stop context/cache stats from jumping (subagent usage + stripped context_window) (#1256)

* fix(web): exclude subagent usage from the parent context indicator

The status bar's `ctx N/M` and `cache N` come from latestUsage, which
scans the normalized messages backwards for the most recent usage. That
scan includes sidechain messages, so while a Task subagent runs its
usage — describing the subagent's own, much smaller context — becomes
the parent's numerator, then snaps back when the parent resumes.

The existing `scope_role !== 'child'` guard never fired on any path.
Claude never stamps scope_role (sdkToLogConverter.ts says so outright),
and Codex drops child token_count events in the CLI before they can
reach the web layer, so no producer ever emits 'child'. isSidechain is
the signal that actually survives.

sdkToLogConverter.ts:308-313 already documents this exact reducer
behaviour, but works around only the denominator by forcing the main
session's context_window onto sidechain messages. The numerator was
left unguarded.

* fix(cli): stop stripping context_window from local-session usage

UsageSchema is a plain z.object, so Zod's default strip mode drops every
undeclared key. sessionScanner forwards parsed.data rather than the raw
line, so on the local-JSONL path usage is truncated to the five declared
fields and context_window — injected on the SDK path by
sdkToLogConverter — never survives.

The web status bar then falls back to getContextBudgetTokens, which
subtracts a 10k headroom, so the same model reports a 1.0M denominator
on a remote session and 990k on a local one.

RawMessageSchema right below already carries .passthrough() with a
comment about losing message.model and messageId the same way; the
nested usage object just never got the same treatment.
This commit is contained in:
Haoqing Wang
2026-07-30 23:22:41 +08:00
committed by GitHub
parent a742fdf1a8
commit eaf5ac49bd
3 changed files with 67 additions and 4 deletions
+6 -1
View File
@@ -6,13 +6,18 @@
import { z } from "zod";
// Usage statistics for assistant messages - used in apiSession.ts
// `passthrough` for the same reason as RawMessageSchema below: the SDK path
// injects `context_window` onto this object (sdkToLogConverter.ts) and Anthropic
// keeps adding usage breakdowns. Under Zod's default `strip`, the local-JSONL
// path silently dropped `context_window`, which made the web status bar fall
// back to a heuristic denominator for local sessions only.
export const UsageSchema = z.object({
input_tokens: z.number().int().nonnegative(),
cache_creation_input_tokens: z.number().int().nonnegative().optional(),
cache_read_input_tokens: z.number().int().nonnegative().optional(),
output_tokens: z.number().int().nonnegative(),
service_tier: z.string().optional(),
});
}).passthrough();
// `passthrough` keeps fields the SDK adds going forward (e.g. `model`, future
// usage breakdowns) so the hub forwards them verbatim. Without it, Zod's