Skip to content

fix(compaction): recover prepared suffix after summary timeout - #646

Open
madgegja wants to merge 2 commits into
code-yeongyu:mainfrom
madgegja:fix/compaction-prepared-suffix-recovery
Open

fix(compaction): recover prepared suffix after summary timeout#646
madgegja wants to merge 2 commits into
code-yeongyu:mainfrom
madgegja:fix/compaction-prepared-suffix-recovery

Conversation

@madgegja

@madgegja madgegja commented Aug 2, 2026

Copy link
Copy Markdown

Summary

  • validate deterministic fallback fit from reconstructed retained message content instead of stale pre-compaction assistant usage
  • allow pre-prompt deterministic recovery only at the hard input cap while preserving fail-closed behavior below it
  • keep final provider result settlement and the full extension compaction operation inside the 120-second watchdog budget

Regression evidence

The affected 272K-context session retained about 20,105 tokens after compaction, but the previous estimator reused a 252,607-token assistant usage snapshot and reported 255,699 tokens, 83 above the 255,616 threshold. The rebuilt branch accepts that real prepared suffix and still rejects a genuinely unsafe 20,000-token window.

Validation

  • vitest --run on the three changed compaction suites: 39 tests passed
  • tsgo --noEmit
  • Biome check on all changed TypeScript files
  • ordered root npm run build
  • replayed saved session 019fbd3f-bb18-7329-81f8-6baf64c25bda against the built artifact

Summary by cubic

Recover prepared suffix after summarization timeout by computing fit from retained message content instead of stale assistant usage. Keep final provider result settlement inside the 120s watchdog and apply the same budget across the full extension compaction operation to prevent stuck sessions.

  • Bug Fixes
    • Deterministic fallback: recompute token fit from retained messages; allow pre_prompt recovery only at the hard input cap; optional pre-prompt below the cap stays fail-closed; latest request is preserved.
    • Watchdog: include responseStream.result() in the 120s budget; abort with StreamDurationBudgetError when the final result lingers; extension compaction now bounds credential resolution and stream setup under the same budget, with caller abort taking precedence.
    • Tests: added coverage for suffix recovery with stale assistant usage, pre-prompt gating at the cap, and final-result settlement timing.

Written for commit d33c1cb. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/coding-agent/src/core/extensions/builtin/compaction/deterministic-fallback.ts">

<violation number="1" location="packages/coding-agent/src/core/extensions/builtin/compaction/deterministic-fallback.ts:89">
P2: The retained-fit gate now sums content-based estimateTokens instead of using the provider-recorded assistant usage. This is the right fix for the stale-usage overcount, but estimateTokens undercounts base64-heavy assistant content: for assistant role it adds raw text/thinking length, whereas for user/toolResult/bashExecution it applies the 4x base64 weighting (weightedChars). A retained suffix whose bulk is a base64-laden assistant message (inline screenshot or dense payload in assistant text/thinking) can therefore pass the `retainedTokens <= contextWindow - reserveTokens` check even though the provider would overflow, contradicting the fail-closed 'preserves the latest request' guarantee this recovery is meant to enforce. Consider weighting base64 runs in the assistant text/thinking arms of estimateTokens (or applying weightedChars there) before relying on it for this safety boundary.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

buildSessionContext([...branchEntries, syntheticCompaction]).messages,
).tokens;
const retainedTokens = buildSessionContext([...branchEntries, syntheticCompaction]).messages.reduce(
(tokens, message) => tokens + estimateTokens(message),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The retained-fit gate now sums content-based estimateTokens instead of using the provider-recorded assistant usage. This is the right fix for the stale-usage overcount, but estimateTokens undercounts base64-heavy assistant content: for assistant role it adds raw text/thinking length, whereas for user/toolResult/bashExecution it applies the 4x base64 weighting (weightedChars). A retained suffix whose bulk is a base64-laden assistant message (inline screenshot or dense payload in assistant text/thinking) can therefore pass the retainedTokens <= contextWindow - reserveTokens check even though the provider would overflow, contradicting the fail-closed 'preserves the latest request' guarantee this recovery is meant to enforce. Consider weighting base64 runs in the assistant text/thinking arms of estimateTokens (or applying weightedChars there) before relying on it for this safety boundary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/coding-agent/src/core/extensions/builtin/compaction/deterministic-fallback.ts, line 89:

<comment>The retained-fit gate now sums content-based estimateTokens instead of using the provider-recorded assistant usage. This is the right fix for the stale-usage overcount, but estimateTokens undercounts base64-heavy assistant content: for assistant role it adds raw text/thinking length, whereas for user/toolResult/bashExecution it applies the 4x base64 weighting (weightedChars). A retained suffix whose bulk is a base64-laden assistant message (inline screenshot or dense payload in assistant text/thinking) can therefore pass the `retainedTokens <= contextWindow - reserveTokens` check even though the provider would overflow, contradicting the fail-closed 'preserves the latest request' guarantee this recovery is meant to enforce. Consider weighting base64 runs in the assistant text/thinking arms of estimateTokens (or applying weightedChars there) before relying on it for this safety boundary.</comment>

<file context>
@@ -85,9 +85,10 @@ export function createRequiredCompactionFallback(
-		buildSessionContext([...branchEntries, syntheticCompaction]).messages,
-	).tokens;
+	const retainedTokens = buildSessionContext([...branchEntries, syntheticCompaction]).messages.reduce(
+		(tokens, message) => tokens + estimateTokens(message),
+		0,
+	);
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant