Skip to content

fix(compaction): clear stale usage.totalTokens after compaction to prevent double-compact#26529

Closed
Sid-Qin wants to merge 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/double-compaction-stale-tokens
Closed

fix(compaction): clear stale usage.totalTokens after compaction to prevent double-compact#26529
Sid-Qin wants to merge 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/double-compaction-stale-tokens

Conversation

@Sid-Qin
Copy link
Contributor

@Sid-Qin Sid-Qin commented Feb 25, 2026

Summary

  • Problem: After auto-compaction, preserved assistant messages retain their original usage.totalTokens from before compaction. The upstream _checkCompaction() reads this stale value and immediately triggers a second compaction that destroys all preserved messages.
  • Why it matters: Users lose their entire conversation context — compaction is supposed to preserve recent messages but the double-compact wipes everything.
  • What changed:
    • src/agents/pi-embedded-subscribe.handlers.compaction.ts — added clearStaleUsageOnPreservedMessages() function, called in handleAutoCompactionEnd when willRetry is false. Deletes usage.totalTokens from preserved assistant messages so the upstream check skips the stale guard.
    • src/plugins/wired-hooks-compaction.test.ts — added 2 new tests: one verifying totalTokens is cleared after final compaction, another verifying it is NOT cleared when willRetry is true.
  • What did NOT change: Compaction logic itself; inputTokens/outputTokens fields; retry behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Compaction no longer triggers a second spurious compaction that destroys all preserved messages.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Runtime: Node.js
  • Integration/channel: Any

Steps

  1. Start a long conversation that triggers auto-compaction
  2. After compaction completes, observe whether a second compaction fires

Expected

  • Single compaction preserves recent messages.

Actual

  • Before fix: Immediate second compaction destroys all preserved messages.
  • After fix: Only one compaction runs; preserved messages retain inputTokens/outputTokens but have totalTokens cleared.

Evidence

2 new tests pass in wired-hooks-compaction.test.ts:
- "clears stale usage.totalTokens from preserved assistant messages after final compaction"
- "does not clear usage.totalTokens when willRetry is true"

Human Verification (required)

  • Verified scenarios: Final compaction clears totalTokens; retry does not clear; other usage fields preserved; non-assistant messages untouched.
  • Edge cases checked: Empty messages array; missing usage object.
  • What I did not verify: Live multi-turn conversation triggering compaction.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: Revert the commit.
  • Files/config to restore: src/agents/pi-embedded-subscribe.handlers.compaction.ts
  • Known bad symptoms: If totalTokens is needed for other purposes after compaction, clearing it could affect downstream consumers — but the upstream _checkCompaction is the only known consumer.

Risks and Mitigations

  • Risk: Other code might read usage.totalTokens after compaction. Mitigation: Only _checkCompaction uses this field for the compaction guard; inputTokens/outputTokens are preserved for usage tracking.

Greptile Summary

Adds clearStaleUsageOnPreservedMessages() to remove stale usage.totalTokens from assistant messages after compaction completes. The upstream _checkCompaction() guard in pi-agent-core reads totalTokens to determine if another compaction is needed; stale pre-compaction values trigger a spurious second compaction that destroys all preserved messages.

Key changes:

  • Only clears totalTokens when willRetry is false (final compaction)
  • Preserves inputTokens and outputTokens for usage tracking
  • Tested for both final and retry scenarios

The fix is minimal and targeted. The totalTokens field is derived from inputTokens + outputTokens in usage normalization (src/agents/usage.ts:81), so removing it after compaction doesn't affect downstream usage tracking which can still use the component fields.

Confidence Score: 4/5

  • Safe to merge with minor caveats around external totalTokens consumers
  • The fix is well-targeted and addresses a specific bug in the compaction flow. Tests verify both the fix (clearing on final compaction) and the non-regression (not clearing on retry). The inputTokens/outputTokens fields are preserved, so usage tracking remains intact. However, deducting one point because: (1) external code might rely on totalTokens being present after compaction (though the PR notes only _checkCompaction is known to use it), and (2) the fix relies on understanding of upstream pi-agent-core behavior that isn't directly tested in this codebase
  • No files require special attention

Last reviewed commit: cd22262

…event double-compact

After compaction, preserved assistant messages still carried their
original usage.totalTokens.  When a subsequent prompt() call hit
_findLastAssistantMessage() → _checkCompaction(), the stale value
exceeded the shouldCompact threshold and triggered an immediate
second compaction that destroyed all preserved messages (0 kept).

Clear usage.totalTokens from preserved assistant messages in
handleAutoCompactionEnd when willRetry is false, so the upstream
_checkCompaction guard sees no token count and skips the check.

Closes openclaw#26458

Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes TS2352 type error in clearStaleUsageOnPreservedMessages.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jalehman
Copy link
Contributor

jalehman commented Mar 1, 2026

A fix for this issue has landed upstream in another PR, closing.

@jalehman jalehman closed this Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Double compaction destroys all preserved messages — stale usage.totalTokens from kept assistant triggers immediate re-compaction

2 participants