Skip to content

feat: Sentry error reporting across all three processes - #90

Merged
dubadub merged 11 commits into
mainfrom
feat/sentry-error-reporting
Aug 13, 2026
Merged

feat: Sentry error reporting across all three processes#90
dubadub merged 11 commits into
mainfrom
feat/sentry-error-reporting

Conversation

@dubadub

@dubadub dubadub commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Adds opt-out error reporting to the packaged app. Closes #88.

Design: docs/superpowers/specs/2026-08-12-sentry-error-reporting-design.md
Plan: docs/superpowers/plans/2026-08-12-sentry-error-reporting.md

Why

Diagnosing #86 (Cookbot is busy right now) took a dive into the cookbot server source in a sibling checkout, because the editor persists no log and reports nothing. ~/.theia/logs/<timestamp>/ is created but never written to, and a Finder-launched app sends console.error to a stdout nobody captures.

Note that server-side Sentry would not have caught it either: a quota rejection is an expected tonic::Status, never an exception. The gap was specifically client-side.

Three processes, two SDKs

The Electron app runs three processes, and the one that matters most is not covered by @sentry/electron:

Process SDK
Electron main @sentry/electron/main
Renderer @sentry/electron/renderer (+ contributed preload bridge for contextIsolation)
Forked backend (fork() at electron-main-application.ts:738) @sentry/node

@sentry/node is pinned to 10.67.0 because @sentry/electron@7.16.0 depends on that exact version; a mismatch loads two copies of @sentry/core.

Four separate theiaExtensions entries, not one object with four keys — targets in the same entry replace rather than merge, which has silently dropped a module here before.

Scope change during implementation

The spec said "unhandled errors". That would not have met its own goal. Sentry's defaults are onUncaughtException + onUnhandledRejection only, and every Cookbot failure is caught and rendered as a chat message — so nothing would have been reported, and #88 would have stayed open with the feature apparently shipped.

Fixed by handing caught failures over explicitly, with mechanism and policy separated:

  • MechanismErrorReporter in the telemetry package. Carries no policy; no-ops when there is no client.
  • PolicyCookbotError.isExpected in cooklang-ai, which owns the knowledge. Spent quota, plan without AI, dropped connection, expired session, context-window overflow and oversized message are normal outcomes and are not reported, or they would bury real defects.

Injection is @optional(), so the language model works without the telemetry extension (tested).

The spec has been amended to record this rather than left describing something we didn't build.

Privacy

Opt-out via cooklang.telemetry.errorReporting.enabled (takes effect on restart; the flag is mirrored to ~/.theia/cook-telemetry.json because Sentry initialises before preferences exist, in two processes that have no preference service).

Scrubbing uses an allowlist, not a denylist — a leak of recipe content cannot be undone:

  • home directory → ~ in exception values, stack frames (filename and abs_path) and breadcrumbs
  • authToken / Authorization / sessionId / bearer / JWT-shaped values → [redacted] (CookbotGrpcClient holds a live token)
  • extra and contexts allowlisted, so recipeContent and chatPrompt are dropped
  • request always dropped
  • no Sentry.setUser — anonymous crash data rather than per-user records. Correlating with server-side Sentry would be useful but is a separate privacy decision.

Development builds stay silent unless COOK_TELEMETRY_DEV=1.

Verification

  • 43 tests in cooklang-ai (7 new for the report/don't-report policy), 28 in cooklang-telemetry
  • npm run compile — 47 projects; lint clean
  • Bundled backend smoke-tested with Sentry.init active: loads and starts clean. I checked this specifically because the backend bundle pulls in require-in-the-middle (Sentry's auto-instrumentation), the same dynamic-require pattern that broke protobufjs here before — no webpack externals entry is needed
  • asarUnpack needs no change: @sentry/node-native is an optional peer, so no native binary ships
  • Packaged .dmg inspected: 48 cooklang-telemetry/lib entries, 4645 @sentry/*, and the packaged preload really calls the bridge
  • Renderer reporting confirmed working end-to-end in the packaged build

Not verified

Being explicit rather than implying a full pass:

  • Backend and Electron-main reporting are unverified. There is currently no way to trigger a reportable failure in either from outside: a dead COOKBOT_ADDRESS yields UNAVAILABLE, which the policy correctly treats as expected and does not report. A Cook Editor: Send Test Error diagnostic command would make this repeatable for every release — worth doing as a follow-up.
  • The full npm run test:theia was never run locally: Node 22+ disappeared from my machine mid-session, leaving only v20.19.1, which the guard from fix: make npm run test runnable and enforce it in CI #85 correctly refuses. Per-package runs pass. CI runs the full suite, so this PR is the first real check of that gate.

dubadub added 11 commits August 12, 2026 16:46
Opt-out error reporting across the three processes the Electron app runs.
The forked backend needs @sentry/node rather than @sentry/electron, and is
both the process where the errors we care about happen and the one most
likely to break when bundled.
Every package in packages/ has one; theiaext lint fails without it. The
plan's scaffold task omitted it, so fix the plan too.
…reference

The forked backend uses @sentry/node; Electron main and the renderer use
@sentry/electron, bridged through a contributed preload module because
Theia runs the renderer with contextIsolation.

PreferenceChange omits newValue, so the writer re-reads the resolved
preference instead - which also handles scope correctly.
Sentry only captures unhandled exceptions on its own, but the failures a
user actually reports are caught and rendered as a chat message - so
nothing was reporting them. That left the feature unable to answer the
question that motivated it (#88).

The language model now hands caught failures to an optional ErrorReporter.
Policy stays with the code that understands the errors: CookbotError.isExpected
excludes spent quotas, missing AI plans, dropped connections, expired
sessions and context-window overflows, so real defects are not buried.

The telemetry package supplies only the mechanism, and the injection is
@optional() so the language model keeps working without it.
The original goal said 'unhandled errors', which would not have met the
goal - Sentry's defaults never see the caught failures users report.
@dubadub
dubadub merged commit e70efa7 into main Aug 13, 2026
2 checks passed
@dubadub
dubadub deleted the feat/sentry-error-reporting branch August 13, 2026 13:25
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.

Packaged editor has no error reporting: user-facing failures leave no diagnosable trace

1 participant