Skip to content

fix(desktop): classify ENOENT spawn failures as PRECONDITION_FAILED, not 500 - #6388

Merged
saddlepaddle merged 1 commit into
mainfrom
sentry/desktop-openapp-enoent
Aug 12, 2026
Merged

saddlepaddle merged 1 commit into
mainfrom
sentry/desktop-openapp-enoent

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Triggering "Open in " for an editor whose CLI launcher is not installed (or not on PATH) reports to Sentry as an INTERNAL_SERVER_ERROR on external.openInApp (DESKTOP-QZ, 10 events on 1.20.2). A missing external application is an expected user-environment condition, not a bug — the guard and its user-facing message are correct, only the classification is wrong.

Root cause

spawnAsync in apps/desktop/src/lib/trpc/routers/external/helpers.ts wraps every child.on("error") into a plain new Error(...), dropping the ENOENT code. The tRPC boundary then defaults the plain error to INTERNAL_SERVER_ERROR, which the dumb 500=report Sentry middleware (apps/desktop/src/lib/trpc/index.ts) always captures.

Fix

In the shared spawnAsync error handler (used by both external.openInApp and external.openFileInEditor via openPathInApp), when the incoming spawn error has code === "ENOENT", reject with new TRPCError({ code: "PRECONDITION_FAILED", cause: { kind: "APP_NOT_INSTALLED", command } }) carrying the exact same message string. Non-ENOENT spawn failures (EACCES, crashes, non-zero exits) keep the plain-Error path and continue reporting as before.

The renderer surfaces this error via toast.error(...error.message...) in every caller (OpenInMenuButton, V2OpenInMenuButton, ClickablePath, usePathActions), and the message is unchanged, so the user-visible toast is identical. No Sentry-side filtering — classification happens at the throw site per the PR #6164 contract.

Verification

$ bunx biome check apps/desktop/src/lib/trpc/routers/external/helpers.ts
Checked 1 file in 17ms. No fixes applied.

$ cd apps/desktop && bun run typecheck
typecheck exit=0

$ bun test src/lib/trpc/routers/external/helpers.test.ts
 113 pass
 0 fail
 120 expect() calls
Ran 113 tests across 1 file. [74.00ms]

Refs DESKTOP-QZ

https://claude.ai/code/session_0968498a-7639-4840-b859-e8aab9fa335f


Summary by cubic

Classifies ENOENT spawn failures from “Open in ” as PRECONDITION_FAILED instead of INTERNAL_SERVER_ERROR, so expected “app not installed/not on PATH” cases no longer get reported to Sentry. Previously we wrapped spawn errors as plain Error, lost the ENOENT code, and tRPC returned a 500; now ENOENT throws a TRPCError with PRECONDITION_FAILED. User-facing toast text is unchanged; non-ENOENT errors behave as before.

Review notes

  • Change is isolated to spawnAsync in apps/desktop/src/lib/trpc/routers/external/helpers.ts, shared by external.openInApp and external.openFileInEditor.
  • For ENOENT only, throw new TRPCError({ code: "PRECONDITION_FAILED", cause: { kind: "APP_NOT_INSTALLED", command } }) from @trpc/server; other spawn errors keep the existing path.
  • No API or UI changes; no rollout or migration required. Refs DESKTOP-QZ.

Written for commit 6b1593d. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling when launching applications that aren’t installed.
    • Displays a clearer installation-related error instead of a generic failure.
    • Preserves existing handling for other application launch errors.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The external helper now maps missing application launchers to a structured PRECONDITION_FAILED tRPC error with APP_NOT_INSTALLED metadata. Other process spawn failures remain generic errors.

Changes

Spawn error handling

Layer / File(s) Summary
Structured spawn error mapping
apps/desktop/src/lib/trpc/routers/external/helpers.ts
spawnAsync distinguishes ENOENT failures from other spawn errors. Missing launchers produce a PRECONDITION_FAILED TRPCError with APP_NOT_INSTALLED metadata. Other failures produce a generic Error.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: aaronrosenthal

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title uses conventional commit format and clearly identifies the main ENOENT classification fix.
Description check ✅ Passed The description explains the problem, root cause, fix, impact, and verification results, although it does not use the template headings exactly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 sentry/desktop-openapp-enoent

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.

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

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/external/helpers.ts (1)

388-402: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add error-path tests for external procedures.

external.openInApp and external.openFileInEditor preserve the TRPCError, and the formatter exposes data.cause.kind. Add tests in helpers.test.ts for ENOENT and non-ENOENT failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/lib/trpc/routers/external/helpers.ts` around lines 388 -
402, Add error-path tests in helpers.test.ts covering external.openInApp and
external.openFileInEditor for both ENOENT and non-ENOENT spawn failures. Assert
ENOENT preserves the TRPCError with PRECONDITION_FAILED and formatter
data.cause.kind set to APP_NOT_INSTALLED, while non-ENOENT failures retain the
generic error behavior.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/external/helpers.ts`:
- Around line 388-402: Add error-path tests in helpers.test.ts covering
external.openInApp and external.openFileInEditor for both ENOENT and non-ENOENT
spawn failures. Assert ENOENT preserves the TRPCError with PRECONDITION_FAILED
and formatter data.cause.kind set to APP_NOT_INSTALLED, while non-ENOENT
failures retain the generic error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f9ffd5e-db21-44fb-bbce-0d5f5af122cf

📥 Commits

Reviewing files that changed from the base of the PR and between b671c27 and 6b1593d.

📒 Files selected for processing (1)
  • apps/desktop/src/lib/trpc/routers/external/helpers.ts

@cubic-dev-ai cubic-dev-ai Bot 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.

No issues found across 1 file

You’re at about 99% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Architecture diagram
sequenceDiagram
    participant UI as Renderer UI
    participant TRPC as tRPC Router
    participant Spawn as spawnAsync Helper
    participant Child as Child Process
    participant Middleware as Sentry Middleware

    Note over UI,Middleware: External App Launch Flow

    UI->>TRPC: invoke "openInApp" / "openFileInEditor"
    TRPC->>Spawn: call openPathInApp(command, args)
    Spawn->>Child: child_process.spawn()

    alt ENOENT error (app not installed / not on PATH)
        Child-->>Spawn: error event with code="ENOENT"
        Spawn->>Spawn: Build user-facing message
        Spawn-->>TRPC: NEW: Reject with TRPCError PRECONDITION_FAILED + cause: APP_NOT_INSTALLED
        TRPC-->>Middleware: Pass error (PRECONDITION_FAILED)
        Middleware->>Middleware: Skip Sentry capture (non-500)
        TRPC-->>UI: Return PRECONDITION_FAILED error
        UI->>UI: Show toast with unchanged error message
    else Other spawn errors (EACCES, crash, non-zero exit)
        Child-->>Spawn: error event with other code
        Spawn->>Spawn: Build user-facing message
        Spawn-->>TRPC: Reject with plain Error
        TRPC-->>Middleware: Pass error (INTERNAL_SERVER_ERROR)
        Middleware->>Middleware: Capture to Sentry
        TRPC-->>UI: Return INTERNAL_SERVER_ERROR
        UI->>UI: Show toast with unchanged error message
    end

    Note over Spawn,Child: Normal successful exit path unchanged
Loading

Re-trigger cubic

@saddlepaddle
saddlepaddle merged commit aabf2e7 into main Aug 12, 2026
12 checks passed
@saddlepaddle
saddlepaddle deleted the sentry/desktop-openapp-enoent branch August 12, 2026 17:48
@github-actions

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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