Skip to content

fix(compact): surface real error in toast instead of swallowing it#950

Merged
pedramamini merged 2 commits into
mainfrom
fix/949-compaction-error-message
Jun 9, 2026
Merged

fix(compact): surface real error in toast instead of swallowing it#950
pedramamini merged 2 commits into
mainfrom
fix/949-compaction-error-message

Conversation

@pedramamini

@pedramamini pedramamini commented May 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

When Compact & Continue failed, the toast told users to "Check the tab for details" — but no tab actually rendered details. The source tab's SummarizeProgressOverlay only renders while isSummarizing (state 'summarizing') is true, so the post-failure error state never reaches the screen. Meanwhile, a bare catch in summarizeContext discarded the underlying error and re-threw a generic "Context summarization failed", hiding the real cause from both the toast and Sentry.

This is the path #949 is hitting: the user sees "Failed to compact context. Check the tab for details." and clicking the source tab shows nothing.

Changes

  • src/renderer/services/contextSummarizer.ts — preserve the underlying Error (rethrow original) so callers see the real cause (Agent X is not available, spawn ENAMETOOLONG, etc.) instead of a generic wrapper string.
  • src/renderer/hooks/agent/useSummarizeAndContinue.ts — read the actual error out of operationStore (and err.message in the unexpected-throw branch) and put it in the toast message. The toast no longer references a phantom "tab for details".
  • src/__tests__/renderer/services/contextSummarizer.test.ts — update the IPC-error test to assert the inner error propagates.

Test plan

  • vitest run src/__tests__/renderer/services/contextSummarizer src/__tests__/renderer/hooks/useSummarizeHandler — all 39 tests pass
  • tsc -p tsconfig.lint.json clean
  • eslint src/renderer/services/contextSummarizer.ts src/renderer/hooks/agent/useSummarizeAndContinue.ts clean
  • prettier --check on touched files clean
  • Manual: trigger a failing compaction (e.g. with a configured-but-unavailable agent) and confirm the toast shows Failed to compact context: <real reason> rather than Check the tab for details

closes #949

Summary by CodeRabbit

  • Bug Fixes
    • Improved error reporting for context summarization and summarize-and-continue flows: user-facing failure messages now surface the original, trimmed error details (including per-tab errors) instead of a generic message.
  • Tests
    • Updated tests to assert and verify propagation of the original error messages for IPC and summarization failures.

When Compact & Continue failed, the toast told users to "Check the
tab for details" but no tab actually rendered details — the source
tab's overlay only renders while state === 'summarizing', not on
error. Meanwhile the underlying error was discarded by a bare catch
in summarizeContext that re-threw a generic "Context summarization
failed", hiding the real cause from both the toast and Sentry.

Stop wrapping with a generic message: rethrow the original Error so
"Agent X is not available", spawn ENAMETOOLONG, etc. propagate up.
The Compaction Failed toast now reads "Failed to compact context:
<actual cause>" so users can act on the message without hunting for
a phantom details tab.

closes #949
@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac334d21-b3b2-49af-80cc-9505f729fb1b

📥 Commits

Reviewing files that changed from the base of the PR and between 11f13e2 and 4e918b3.

📒 Files selected for processing (1)
  • src/__tests__/integration/contextSummarizer.integration.test.ts

📝 Walkthrough

Walkthrough

Preserve original errors thrown during context summarization and surface per-tab error details in the Compact-and-Continue UI flow. The service rethrows original Error objects, the hook reads per-tab operationStore error details for toasts and builds unexpected-error messages from caught error messages, and tests assert the propagated IPC message.

Changes

Error Message Preservation for Context Summarization

Layer / File(s) Summary
Service error handling
src/renderer/services/contextSummarizer.ts
summarizeContext() now rethrows original Error instances when caught; non-Error/non-string caught values are wrapped with Context summarization failed.
Hook integration & toast display
src/renderer/hooks/agent/useSummarizeAndContinue.ts
When startSummarize returns null, the hook reads summarizeStates.get(targetTabId)?.error from operationStore and includes the trimmed detail in the failure toast; unexpected-error .catch uses err instanceof Error ? err.message : String(err) to build the toast message.
Test verification
src/__tests__/renderer/services/contextSummarizer.test.ts, src/__tests__/integration/contextSummarizer.integration.test.ts
Unit and integration tests updated to expect the underlying IPC error message ('IPC failed') propagates through rather than being masked by a generic wrapper message.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble traces of an err,
No longer wrapped in foggy fur;
The toast now speaks the truth it found,
Per-tab whispers echo round.
Hop on — the messages are clear!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: improving error handling by surfacing the real error message instead of a generic wrapper.
Linked Issues check ✅ Passed The PR addresses all coding requirements from #949: rethrows original errors in contextSummarizer.ts, reads actual errors from operationStore in useSummarizeAndContinue.ts, and updates tests accordingly.
Out of Scope Changes check ✅ Passed All changes are directly aligned with fixing the error handling bug in #949; no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/949-compaction-error-message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a "silent failure" bug in Compact & Continue: the catch block in contextSummarizer.ts was swallowing the real error and re-throwing a generic string, while the hook's toast was pointing users at a tab that never rendered any details. The fix re-throws the original Error and reads the actual error message from operationStore to display it directly in the toast.

  • One P2 nit in the .catch branch of useSummarizeAndContinue.ts: String(err) for non-Error thrown values can produce \"null\" or \"[object Object]\" in the user-facing toast message.

Confidence Score: 4/5

Safe to merge; the fix is targeted and correct, with one minor P2 nit around non-Error throw handling in the catch branch.

All findings are P2. The core fix — re-throwing the original Error and reading it from the store for the toast — is correct and well-tested. The only nit is that String(err) in the .catch branch can produce noisy values like 'null' in the toast for exotic (non-Error, non-string) throws, which is an edge case unlikely to occur in practice.

No files require special attention; the one nit is in useSummarizeAndContinue.ts line 425.

Important Files Changed

Filename Overview
src/renderer/services/contextSummarizer.ts Catch block now re-throws original Error instances and converts string throws — correctly surfaces real failure causes instead of a generic wrapper message.
src/renderer/hooks/agent/useSummarizeAndContinue.ts Toast messages now include the real error detail pulled from operationStore or the thrown Error; minor nit: String(err) on non-Error/non-string values may produce noise like '[object Object]' in the catch branch.
src/tests/renderer/services/contextSummarizer.test.ts Test correctly updated to assert the original IPC error message propagates rather than the previous generic wrapper.

Reviews (1): Last reviewed commit: "fix(compact): surface real error in toas..." | Re-trigger Greptile

Comment on lines +425 to +431
const detail = err instanceof Error ? err.message : String(err);
notifyToast({
type: 'error',
title: 'Compaction Failed',
message: 'An unexpected error occurred during compaction.',
message: detail
? `An unexpected error occurred during compaction: ${detail}`
: 'An unexpected error occurred during compaction.',

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 String(err) may surface noise in the toast

String(err) on a non-Error thrown value produces strings like "[object Object]", "null", or "undefined" that would appear verbatim in the user-facing toast. The existing ternary only guards against an empty string, but String(null)"null" is truthy, so those internal values still reach the UI. Consider typeof err === 'string' ? err : '' as the non-Error fallback so only genuine string throws pass through.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/hooks/agent/useSummarizeAndContinue.ts`:
- Around line 425-431: The fallback message currently uses String(err) which
renders plain objects as "[object Object]" and hides useful info; update the
error serialization in useSummarizeAndContinue.ts (the block that sets detail
from err and calls notifyToast) to safely stringify non-Error thrown values: if
err is an Error use err.message, else if typeof err === 'object' and err !==
null use a JSON.stringify(err) (with try/catch and a length cap) to extract
useful fields, otherwise fall back to String(err); ensure the resulting detail
is used in the notifyToast call so the toast shows meaningful error information.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9b90bf1f-852f-417e-b20f-8514b098e2d0

📥 Commits

Reviewing files that changed from the base of the PR and between d0e1363 and 11f13e2.

📒 Files selected for processing (3)
  • src/__tests__/renderer/services/contextSummarizer.test.ts
  • src/renderer/hooks/agent/useSummarizeAndContinue.ts
  • src/renderer/services/contextSummarizer.ts

Comment on lines +425 to +431
const detail = err instanceof Error ? err.message : String(err);
notifyToast({
type: 'error',
title: 'Compaction Failed',
message: 'An unexpected error occurred during compaction.',
message: detail
? `An unexpected error occurred during compaction: ${detail}`
: 'An unexpected error occurred during compaction.',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid stringifying arbitrary thrown values.

String(err) turns plain-object failures into [object Object], so the fallback toast can still hide the real cause.

♻️ Small improvement for unexpected-error messaging
-					const detail = err instanceof Error ? err.message : String(err);
+					const detail =
+						err instanceof Error
+							? err.message
+							: typeof err === 'object' && err !== null && 'message' in err
+								? String((err as { message?: unknown }).message ?? '')
+								: typeof err === 'string'
+									? err
+									: '';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const detail = err instanceof Error ? err.message : String(err);
notifyToast({
type: 'error',
title: 'Compaction Failed',
message: 'An unexpected error occurred during compaction.',
message: detail
? `An unexpected error occurred during compaction: ${detail}`
: 'An unexpected error occurred during compaction.',
const detail =
err instanceof Error
? err.message
: typeof err === 'object' && err !== null && 'message' in err
? String((err as { message?: unknown }).message ?? '')
: typeof err === 'string'
? err
: '';
notifyToast({
type: 'error',
title: 'Compaction Failed',
message: detail
? `An unexpected error occurred during compaction: ${detail}`
: 'An unexpected error occurred during compaction.',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/agent/useSummarizeAndContinue.ts` around lines 425 - 431,
The fallback message currently uses String(err) which renders plain objects as
"[object Object]" and hides useful info; update the error serialization in
useSummarizeAndContinue.ts (the block that sets detail from err and calls
notifyToast) to safely stringify non-Error thrown values: if err is an Error use
err.message, else if typeof err === 'object' and err !== null use a
JSON.stringify(err) (with try/catch and a length cap) to extract useful fields,
otherwise fall back to String(err); ensure the resulting detail is used in the
notifyToast call so the toast shows meaningful error information.

@chr1syy chr1syy left a comment

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.

Thanks for tackling this — the diagnosis is spot-on. I confirmed both bugs are still present on main and rc (the bare catch in summarizeContext and the toast pointing at a SummarizeProgressOverlay that only renders while state === 'summarizing'). The fix matches the codebase's "don't silently swallow exceptions" guidance in CLAUDE.md.

A few things to clear before this is mergeable.

Blockers

1. Merge conflicts on both branches. mergeable: CONFLICTING. I reproduced locally:

  • Against main: conflict in src/__tests__/renderer/services/contextSummarizer.test.ts because a new test 'should handle an empty groomed response' was added directly above the renamed test.
  • Against rc (if retargeted): conflicts in useSummarizeAndContinue.tsrc swapped console.errorlogger.error on the same line you touch — plus the async prompt-loading refactor that landed in contextSummarizer.ts.

Both sets of conflicts are mechanical.

2. lint-and-format CI failing — but not from this PR. prettier --check . flagged docs/releases.md and src/__tests__/renderer/hooks/useAgentListeners.test.ts. Both pass at current main HEAD and the PR's three touched files pass prettier cleanly. This is stale-base inheritance — a rebase + prettier --write . clears it.

3. String(err) fallback can leak "[object Object]" / "null" into the toast. Both Greptile and CodeRabbit flagged this and they're right. At useSummarizeAndContinue.ts:425:

const detail = err instanceof Error ? err.message : String(err);

String({ foo: 1 })"[object Object]" (truthy, slips past the ternary). String(null)"null" (also truthy). Suggested shape:

const detail =
  err instanceof Error
    ? err.message
    : typeof err === 'string'
      ? err
      : typeof err === 'object' && err !== null && 'message' in err
        ? String((err as { message?: unknown }).message ?? '')
        : '';

Base branch — likely should be rc

The PR currently targets main, but recent bug-fix PRs all land on rc (#1066, #1067, #1043, #1023, #1021) so they ride the next release. Functionally the rewritten lines are identical on both branches — this is convention, not correctness.

Non-blocking observations

  • contextSummarizer.ts fallback has the same [object Object] risk. throw new Error(typeof err === 'string' ? err : 'Context summarization failed') silently drops detail for non-Error non-string throws. Worth tightening alongside the toast fix.
  • No length cap on detail. A multi-line spawn error or long Error.message produces an ugly oversized toast. A short cap (e.g. ~200 chars + ellipsis) would be friendlier — and matches how other Maestro toasts handle long error text.
  • Reaching into operationStore for the error. useOperationStore.getState().summarizeStates.get(targetTabId)?.error works because the store update happens synchronously inside startSummarize before it resolves null, but it couples the toast to store internals. Returning the error from startSummarize would be cleaner. Fine to defer.

Verdict

Right idea, right files, real bug. Ship-blocking items are (1) rebase to clear conflicts + stale lint, (2) the String(err) hardening. I'd retarget to rc while you're rebasing.

Resolve semantic conflicts from PR #950's error-surfacing change:
- contextSummarizer.test.ts: keep main's empty-groomed-response test plus
  the PR's renamed IPC-error-propagation test (identical assertion body).
- useSummarizeHandler.test.ts: update two toast expectations to the new
  messages that surface the real error instead of 'Check the tab for details'.
- contextSummarizer.integration.test.ts: assert the inner 'IPC failed' error
  propagates now that the wrapper string is gone.
@pedramamini pedramamini merged commit f166b93 into main Jun 9, 2026
2 of 3 checks passed
@pedramamini pedramamini deleted the fix/949-compaction-error-message branch June 9, 2026 15:33
pedramamini added a commit that referenced this pull request Jun 13, 2026
Take RC's tree wholesale. main's 5 commits since the merge base are the
test-coverage campaign (#1040, #1053) plus generated audit docs, all
written against the pre-cutover file layout that RC has since
restructured. Obsolete against RC's commits. The one production fix on
main (#950, surface real compaction error) is applied as a follow-up.
pedramamini added a commit that referenced this pull request Jun 13, 2026
Apply the production-code portion of main's #950 onto the v0.17.0 cutover.
Kept RC's logger.error style and added the detail extraction so the
Compaction Failed toast reads 'Failed to compact context: <cause>'
instead of pointing users at a phantom details tab. contextSummarizer
now rethrows the original Error rather than a generic wrapper.

Updated RC's contextSummarizer test to assert the underlying error is
surfaced ('IPC failed') instead of the old generic wrapper. closes #949
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.

Bug: Compact and Continue always fails with COMPACTION FAILED error; detai...

2 participants