Skip to content

fix(status): avoid misleading context usage when totalTokensFresh=false#45270

Closed
xyooz wants to merge 2 commits intoopenclaw:mainfrom
xyooz:fix/status-totalTokensFresh
Closed

fix(status): avoid misleading context usage when totalTokensFresh=false#45270
xyooz wants to merge 2 commits intoopenclaw:mainfrom
xyooz:fix/status-totalTokensFresh

Conversation

@xyooz
Copy link
Copy Markdown

@xyooz xyooz commented Mar 13, 2026

Summary

Fix status context rendering when session token totals are stale.

When totalTokensFresh=false, status previously fell back to inputTokens + outputTokens, which can overstate context usage and report misleading values like 299k/200k (149%).

This change avoids that fallback for stale totals and reports unknown context occupancy unless fresh totals (or transcript usage) are available.

Changes

  • src/auto-reply/status.ts
    • Stop inferring totalTokens from input+output when totalTokensFresh is explicitly false.
    • Preserve transcript-usage override behavior so runtime can still show a concrete usage value when available.
  • src/auto-reply/status.test.ts
    • Add regression test covering totalTokensFresh=false to ensure status shows unknown context usage instead of synthetic input+output usage.

User impact

  • Prevents false alarms that auto-compaction is broken.
  • Makes /status context line semantically accurate in stale-token states.

Validation

  • pnpm vitest run src/auto-reply/status.test.ts
  • Result: all tests passed locally.

Notes

This is a display/accounting correctness fix; it does not alter compaction trigger behavior itself.

Fixes #45268

Copilot AI review requested due to automatic review settings March 13, 2026 16:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts /status context rendering so it doesn’t display misleading “context used” values when the session’s stored token total is known to be stale (totalTokensFresh=false). Instead, it shows unknown context occupancy unless a fresh total is available (including via transcript-derived usage).

Changes:

  • Stop deriving totalTokens from inputTokens + outputTokens when the stored total is explicitly stale (and generally avoid synthesizing totals for status).
  • Preserve transcript-usage override behavior and treat transcript-derived totals as fresh for display purposes.
  • Add a regression test ensuring stale totals render as unknown context usage.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/auto-reply/status.ts Prevents status from displaying synthesized/overstated context usage when totals are stale; keeps transcript override as the source of truth when enabled.
src/auto-reply/status.test.ts Adds coverage for totalTokensFresh=false to ensure /status shows ?/context instead of a synthetic total.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 13, 2026

Greptile Summary

This PR fixes a display correctness bug in /status where context occupancy was overstated because totalTokens was synthesized from inputTokens + outputTokens even when the stored total was explicitly marked stale. The fix returns undefined for stale totals so the context line renders as ?/Nk instead of a misleading inflated percentage.

Key changes:

  • status.ts: totalTokens is now only populated from entry.totalTokens when it is a number and the entry does not mark it stale. The input+output fallback is removed entirely.
  • status.ts (transcript path): The guard that promotes a transcript candidate into totalTokens gains an extra !totalTokensFresh clause. This is logically redundant since totalTokensFresh is derived solely from whether totalTokens is a defined number, so the existing !totalTokens check already covers the same case — but it is harmless.
  • status.test.ts: A regression test confirms the stale-token path renders unknown context usage. An additional test covering the case where totalTokens is explicitly present but stale would give complete branch coverage of the fix.

Confidence Score: 5/5

  • This PR is safe to merge; changes are display-only and well-tested.
  • The fix is narrow and correct — it removes a fallback that was never valid when totals are stale and lets formatTokens emit ?/Nk via its existing null/undefined branch. There is no change to compaction logic. One redundant guard clause was introduced but does not affect behavior. A minor test coverage gap (stale entry with an explicit totalTokens value) is noted but does not risk a regression.
  • No files require special attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/status.ts
Line: 482

Comment:
**Redundant `!totalTokensFresh` guard**

`totalTokensFresh` is defined on line 468 as `typeof totalTokens === "number"`, so it is `false` if and only if `totalTokens` is `undefined`. In that case `!totalTokens` is already truthy (since `undefined` is falsy), making `!totalTokensFresh` redundant in this condition.

The only case where `totalTokens` is a defined value but conceptually "not fresh" is `totalTokens === 0`, which is already covered by the `totalTokens === 0` clause.

This doesn't cause a bug, but the extra clause adds noise. Consider removing it to keep the intent clear:

```suggestion
      if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/auto-reply/status.test.ts
Line: 634-656

Comment:
**Consider adding a test for explicit stale `totalTokens`**

This test covers the case where `totalTokens` is absent from the entry and `totalTokensFresh` is false, which is the original bug scenario. A complementary test where `totalTokens` is explicitly set to a large value *and* marked stale would give fuller coverage of the fix logic at line 464–467 (both branches of the ternary are exercised). The expected result would still be `"Context: ?/32k"` since a stale stored total should be ignored whether or not it is present in the entry.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: d5ddc75

Comment thread src/auto-reply/status.ts Outdated
if (logUsage) {
const candidate = logUsage.promptTokens || logUsage.total;
if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
if (!totalTokens || !totalTokensFresh || totalTokens === 0 || candidate > totalTokens) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant !totalTokensFresh guard

totalTokensFresh is defined on line 468 as typeof totalTokens === "number", so it is false if and only if totalTokens is undefined. In that case !totalTokens is already truthy (since undefined is falsy), making !totalTokensFresh redundant in this condition.

The only case where totalTokens is a defined value but conceptually "not fresh" is totalTokens === 0, which is already covered by the totalTokens === 0 clause.

This doesn't cause a bug, but the extra clause adds noise. Consider removing it to keep the intent clear:

Suggested change
if (!totalTokens || !totalTokensFresh || totalTokens === 0 || candidate > totalTokens) {
if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/status.ts
Line: 482

Comment:
**Redundant `!totalTokensFresh` guard**

`totalTokensFresh` is defined on line 468 as `typeof totalTokens === "number"`, so it is `false` if and only if `totalTokens` is `undefined`. In that case `!totalTokens` is already truthy (since `undefined` is falsy), making `!totalTokensFresh` redundant in this condition.

The only case where `totalTokens` is a defined value but conceptually "not fresh" is `totalTokens === 0`, which is already covered by the `totalTokens === 0` clause.

This doesn't cause a bug, but the extra clause adds noise. Consider removing it to keep the intent clear:

```suggestion
      if (!totalTokens || totalTokens === 0 || candidate > totalTokens) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +634 to +656
it("does not infer context usage from input/output when totalTokens is stale", () => {
const text = buildStatusMessage({
agent: {
model: "anthropic/claude-opus-4-5",
contextTokens: 32_000,
},
sessionEntry: {
sessionId: "stale-total",
updatedAt: 0,
inputTokens: 298_437,
outputTokens: 147,
totalTokensFresh: false,
contextTokens: 32_000,
},
sessionKey: "agent:main:main",
sessionScope: "per-sender",
queue: { mode: "collect", depth: 0 },
includeTranscriptUsage: false,
modelAuth: "api-key",
});

expect(normalizeTestText(text)).toContain("Context: ?/32k");
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test for explicit stale totalTokens

This test covers the case where totalTokens is absent from the entry and totalTokensFresh is false, which is the original bug scenario. A complementary test where totalTokens is explicitly set to a large value and marked stale would give fuller coverage of the fix logic at line 464–467 (both branches of the ternary are exercised). The expected result would still be "Context: ?/32k" since a stale stored total should be ignored whether or not it is present in the entry.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/status.test.ts
Line: 634-656

Comment:
**Consider adding a test for explicit stale `totalTokens`**

This test covers the case where `totalTokens` is absent from the entry and `totalTokensFresh` is false, which is the original bug scenario. A complementary test where `totalTokens` is explicitly set to a large value *and* marked stale would give fuller coverage of the fix logic at line 464–467 (both branches of the ternary are exercised). The expected result would still be `"Context: ?/32k"` since a stale stored total should be ignored whether or not it is present in the entry.

How can I resolve this? If you propose a fix, please make it concise.

@xyooz
Copy link
Copy Markdown
Author

xyooz commented Mar 13, 2026

Addressed review suggestions in a follow-up commit:\n\n- Removed the redundant !totalTokensFresh condition in transcript candidate promotion.\n- Added regression coverage for the explicit stale-total case (totalTokens present with totalTokensFresh=false), which still renders unknown context usage (Context: ?/32k).\n- Re-ran targeted tests: pnpm vitest run src/auto-reply/status.test.ts (pass).\n\nThanks for the review.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 28, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 29, 2026

Thanks for the context here. I did a careful shell check against current main, and this is already implemented.

Close this PR as implemented on current main. The stale /status context display bug is fixed by e5dc0e6d15ea7a49a93ae0e0ea94c50c2c7b2a65, with source changes and regression coverage in the moved src/status/status-message.ts path; the paired bug was also closed by a maintainer with that fix SHA.

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review details

Best possible solution:

Keep the current-main implementation in src/status/status-message.ts, close this superseded source PR, and let the next release carry the fixed /status behavior from e5dc0e6d15ea7a49a93ae0e0ea94c50c2c7b2a65.

Security review:

Security review cleared: The PR only changes status rendering and tests, with no dependency, CI, secret, install, release, or code execution surface changes.

What I checked:

  • Current status rendering: Current main uses resolveFreshSessionTotalTokens(entry), returns unknown context usage when entry.totalTokensFresh === false, and only keeps the input/output fallback when freshness is not explicitly false; transcript-derived totals are also gated off for the stale path. (src/status/status-message.ts:575, 6e5a703dd2db)
  • Freshness contract: resolveFreshSessionTotalTokens rejects stored totals marked with totalTokensFresh === false, which is the helper now used by status rendering. (src/config/sessions/types.ts:485, 6e5a703dd2db)
  • Regression tests: Current tests cover stale stored totals rendering as Context: ?/... instead of the cumulative token value. (src/auto-reply/status.test.ts:106, 6e5a703dd2db)
  • Transcript fallback coverage: Current tests also ensure transcript fallback does not rehydrate stale context usage when totalTokensFresh=false. (src/auto-reply/status.test.ts:1463, 6e5a703dd2db)
  • Fix provenance: GitHub commit metadata for e5dc0e6d15ea7a49a93ae0e0ea94c50c2c7b2a65 shows fix: expose agent runtime status metadata, committed at 2026-04-29T04:02:04Z; the current changelog entry says it avoids stale or cumulative CLI token totals as live context usage and fixes status: context usage is misleading when totalTokensFresh=false #45268. (CHANGELOG.md:138, e5dc0e6d15ea)
  • Release check: The latest release tag v2026.4.26 points at be8c24633aaa7ef0425ae1178f096ee8dd6226c0, and that tag still contains the old entry.totalTokens ?? input+output fallback, so the fix is on current main only, not yet in a shipped release. (src/status/status-message.ts:574, be8c24633aaa)

Likely related people:

  • steipete: Authored and committed the current-main fix that changed status rendering, added stale-token regression tests, updated the changelog, and closed the paired bug. (role: recent maintainer and fix author; confidence: high; commits: e5dc0e6d15ea, 7a85c1a8226e, 171383928859; files: src/status/status-message.ts, src/auto-reply/status.test.ts, CHANGELOG.md)
  • Takhoffman: Recent commit history for the same status rendering/test area includes /status runner-label work, making this a plausible routing backup for status output behavior. (role: adjacent status maintainer; confidence: medium; commits: 03477ccb82e0; files: src/status/status-message.ts, src/auto-reply/status.test.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 6e5a703dd2db; fix evidence: commit e5dc0e6d15ea, main fix timestamp 2026-04-29T04:02:04Z.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

status: context usage is misleading when totalTokensFresh=false

2 participants