Skip to content

fix: resolve two rc-channel Sentry crashes (MAESTRO-Q8, MAESTRO-QA)#1041

Open
pedramamini wants to merge 2 commits into
rcfrom
fix/sentry-rc-crashes
Open

fix: resolve two rc-channel Sentry crashes (MAESTRO-Q8, MAESTRO-QA)#1041
pedramamini wants to merge 2 commits into
rcfrom
fix/sentry-rc-crashes

Conversation

@pedramamini
Copy link
Copy Markdown
Collaborator

@pedramamini pedramamini commented May 24, 2026

Addresses two field crashes reported on the rc channel (validated via the channel: rc / *-RC release tags in Sentry and by tracing the affected code).

MAESTRO-Q8 — Error: spawn EINVAL (133 events, 100% Windows)

ChildProcessSpawner auto-enables shell on Windows for bare .exe commands and shebang scripts, but not for .cmd/.bat. Since Node's CVE-2024-27980 hardening, child_process.spawn throws spawn EINVAL when launching a batch file without shell: true. npm-installed agent CLIs resolve to shims like claude.cmd / codex.cmd / opencode.cmd, so tab naming (which spawns agents through child_process) failed for every affected Windows user.

Fix: add a .cmd/.bat auto-shell branch (mirroring the existing .exe/shebang branches), and quote command paths containing spaces under the default cmd.exe shell so npm shims under C:\Users\First Last\... resolve correctly.

MAESTRO-QA — SyntaxError: Unterminated string in JSON (25 events)

HistoryManager.getEntries already degrades gracefully to an empty history when a session file is truncated/corrupt — but it also called captureException on the JSON SyntaxError. A history write interrupted by a crash or power loss is an expected, recoverable on-disk condition, not a code bug, so this only buried the Sentry dashboard in non-actionable noise.

Fix: treat SyntaxError as handled (log + return []); genuinely unexpected read failures (permissions, I/O, etc.) are still reported.

Excluded (investigated, not actionable here)

  • MAESTRO-R2 (EPIPE in Logger.addLog): already fixed — the console write is wrapped in try/catch ("Fixes MAESTRO-5C"); the lone event is from a stale 0.15.4-RC build.
  • MAESTRO-NP (WASM CSP): already fixed by 2e369c927 on rc.
  • MAESTRO-JV (missing native bindings): native-module packaging issue, no obvious code fix.
  • MAESTRO-5A / 4Y / H1 / R1 (renderer/native crashes): no obvious code fix; memory-monitoring diagnostics already in place.

Testing

  • New unit tests in ChildProcessSpawner.test.ts (Windows .cmd/.bat shell + path quoting) and history-manager.test.ts (corrupt JSON not reported; real read errors still reported).
  • npm run lint (all 3 tsconfigs) ✅ — vitest for both affected suites: 116 passed ✅

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Session history files with corruption are now recovered gracefully
    • Batch files (.cmd/.bat) now execute reliably on Windows
    • Commands with executable paths containing spaces now work correctly on Windows

Review Change Stack

Node throws "spawn EINVAL" when launching a .cmd/.bat file without a
shell (CVE-2024-27980 hardening). npm-installed agent CLIs resolve to
shims like claude.cmd/codex.cmd on Windows, so tab naming — which spawns
agents through child_process — failed for every Windows user.

Auto-enable shell for .cmd/.bat commands (mirroring the existing .exe and
shebang-script branches) and quote command paths containing spaces under
the default cmd.exe shell so npm shims under "C:\Users\First Last\..."
resolve correctly.

Fixes MAESTRO-Q8 (channel: rc).
HistoryManager.getEntries already degrades to an empty history when a
session file is truncated/corrupt, but it also captured the JSON
SyntaxError to Sentry. A history write interrupted by a crash or power
loss is an expected, recoverable on-disk condition — not a code bug — so
this only buried the dashboard in non-actionable noise. Treat SyntaxError
as handled (log + return []); keep reporting genuinely unexpected read
failures (permissions, I/O, etc.).

Fixes MAESTRO-QA (channel: rc).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces two independent error-handling improvements: History Manager now treats truncated/corrupt JSON history files as recoverable conditions without reporting to Sentry, while ChildProcessSpawner auto-enables shell mode for Windows batch files and defensively quotes command paths containing spaces to prevent cmd.exe path splitting.

Changes

History Manager Corrupt JSON Handling

Layer / File(s) Summary
Corrupt JSON detection and recovery
src/main/history-manager.ts, src/__tests__/main/history-manager.test.ts
getEntries() adds SyntaxError-specific catch that logs a warning and returns empty array for truncated/malformed JSON without Sentry report; other read errors continue to report via captureException() with operation metadata. Tests mock Sentry, verify non-JSON errors trigger captureException, and confirm corrupt JSON does not.

Windows Batch File Shell Spawning

Layer / File(s) Summary
Batch file shell mode and path quoting
src/main/process-manager/spawners/ChildProcessSpawner.ts, src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
spawn() makes spawnCommand mutable for defensive modifications; auto-enables shell mode for .cmd/.bat on Windows when runInShell unset; when using default Windows shell with whitespace in command path, wraps path in quotes to prevent cmd.exe splitting. Tests mock platform detection and verify batch auto-shell behavior and quoting logic across Windows and non-Windows contexts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

ready to merge

Poem

🐰 Corrupt history now rests soft and light,
No Sentry wails for truncated night;
On Windows, batch files find their shell,
And paths with spaces quote well—
Bug fixes hopping toward the bright.

🚥 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 accurately and specifically summarizes the main changes: fixing two Sentry crashes on rc channel with clear issue references (MAESTRO-Q8, MAESTRO-QA).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ 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/sentry-rc-crashes

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
Copy link
Copy Markdown

greptile-apps Bot commented May 24, 2026

Greptile Summary

This PR fixes two field crashes on the rc channel: spawn EINVAL on Windows when npm-installed CLIs resolve to .cmd/.bat shims, and noisy Sentry reporting for truncated history JSON that is a normal on-disk condition after a crash or power loss.

  • MAESTRO-Q8: ChildProcessSpawner gains a .cmd/.bat auto-shell branch (mirroring the existing .exe branch) and a cmd.exe command-path quoting guard for paths containing spaces, so npm shims installed under paths like C:\\Users\\First Last\\... spawn correctly.
  • MAESTRO-QA: HistoryManager.getEntries now intercepts SyntaxError before the generic catch, logging a warning and returning [] without calling captureException; all other I/O errors continue to be reported to Sentry.

Confidence Score: 5/5

Both changes are narrow, well-tested bug fixes with no impact on non-Windows paths and no changes to data persistence logic.

The ChildProcessSpawner changes add two tightly-scoped Windows-only guards: one that mirrors an already-working pattern (auto-shell for .exe → .cmd/.bat), and one that defensively quotes paths containing spaces only when spawning through the default cmd.exe shell. The history-manager change is a single instanceof check that redirects one error type away from Sentry without altering the return value. Both fixes have dedicated unit tests, the existing beforeEach vi.resetAllMocks() ensures mock state is clean between tests, and parseHistoryFileData re-throws the original SyntaxError on unrecoverable input so the new branch is reliably reachable.

No files require special attention.

Important Files Changed

Filename Overview
src/main/history-manager.ts Adds a SyntaxError branch in getEntries() catch block to silently discard truncated/corrupt history files without reporting them to Sentry; genuinely unexpected I/O errors still captured.
src/main/process-manager/spawners/ChildProcessSpawner.ts Adds two new Windows-specific blocks: auto-shell for .cmd/.bat commands (mirrors existing .exe branch), and cmd.exe command-path quoting when the path contains spaces — both necessary for npm-shim CLIs installed in user profiles with spaces.
src/tests/main/history-manager.test.ts Adds Sentry mock and two new test cases: one confirming captureException fires for non-SyntaxError failures, one confirming it does NOT fire for corrupt/truncated JSON.
src/tests/main/process-manager/spawners/ChildProcessSpawner.test.ts Adds a Windows platform mock and five new tests covering .cmd/.bat auto-shell, path quoting with/without spaces, and non-Windows passthrough — all cases exercised with the correct assertions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ChildProcessSpawner.spawn] --> B{isWindows?}
    B -- No --> Z[spawn as-is]
    B -- Yes --> C{commandExt}
    C -- .exe, no path --> D[useShell = true]
    C -- .cmd / .bat --> E[useShell = true - NEW branch]
    C -- no ext + path + shebang --> F[useShell = true]
    C -- other --> G{config.runInShell?}
    G -- Yes --> D
    G -- No --> Z
    D --> H{spawnShell === true AND path has spaces?}
    E --> H
    F --> H
    H -- Yes --> I[Quote spawnCommand - NEW quoting guard]
    H -- No --> J[spawn with shell=true]
    I --> J

    subgraph getEntries
        direction TD
        R[readFile] --> S{parse error?}
        S -- ENOENT --> T[return empty]
        S -- SyntaxError - NEW --> U[logger.warn / return empty / no Sentry]
        S -- other error --> V[logger.warn / captureException / return empty]
        S -- OK --> W[return entries]
    end
Loading

Reviews (1): Last reviewed commit: "fix(history): stop reporting corrupt his..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/process-manager/spawners/ChildProcessSpawner.ts`:
- Around line 329-343: The current quote-guard only runs when spawnShell ===
true and misses cases where spawnShell is a concrete shell string that points to
cmd.exe; update the condition in ChildProcessSpawner around spawnCommand quoting
to also detect explicit cmd.exe shell strings (e.g., check spawnShell === true
OR basename/spawnShell endsWith 'cmd.exe' case-insensitively) so that when
running on Windows and the resolved shell is cmd.exe you still wrap spawnCommand
with quotes if it contains whitespace and isn't already quoted; refer to symbols
spawnShell, spawnCommand and isWindows() when making this change.
🪄 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: 895543a7-112a-44e6-8b1a-e9b019af9676

📥 Commits

Reviewing files that changed from the base of the PR and between 2e369c9 and d3b92d1.

📒 Files selected for processing (4)
  • src/__tests__/main/history-manager.test.ts
  • src/__tests__/main/process-manager/spawners/ChildProcessSpawner.test.ts
  • src/main/history-manager.ts
  • src/main/process-manager/spawners/ChildProcessSpawner.ts

Comment on lines +329 to +343
// When spawning through the default Windows shell (cmd.exe via ComSpec),
// Node concatenates the command and args into a single command line without
// quoting the command itself. A command path that contains spaces — e.g. an
// npm shim under "C:\Users\First Last\AppData\Roaming\npm\claude.cmd" — would
// be split by cmd.exe and fail. Quote it defensively. We only do this for the
// boolean (cmd.exe) shell path; an explicit shell string carries its own
// quoting rules and is the caller's responsibility.
if (
isWindows() &&
spawnShell === true &&
/\s/.test(spawnCommand) &&
!spawnCommand.startsWith('"')
) {
spawnCommand = `"${spawnCommand}"`;
}
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 | 🟠 Major | ⚡ Quick win

Handle explicit cmd.exe shell paths here too.

This quote guard only runs when spawnShell === true, but src/main/ipc/handlers/process.ts:715-731 and 887-904 already thread a concrete shell string through config.shell. If that resolves to cmd.exe, this branch is skipped and a batch shim under a path like C:\Users\First Last\... still gets split by cmd.exe.

Suggested fix
+			const shellBaseName =
+				typeof spawnShell === 'string'
+					? spawnShell.split(/[\\/]/).pop()?.toLowerCase()
+					: undefined;
+
 			if (
 				isWindows() &&
-				spawnShell === true &&
+				(spawnShell === true || shellBaseName === 'cmd.exe' || shellBaseName === 'cmd') &&
 				/\s/.test(spawnCommand) &&
 				!spawnCommand.startsWith('"')
 			) {
 				spawnCommand = `"${spawnCommand}"`;
 			}
📝 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
// When spawning through the default Windows shell (cmd.exe via ComSpec),
// Node concatenates the command and args into a single command line without
// quoting the command itself. A command path that contains spaces — e.g. an
// npm shim under "C:\Users\First Last\AppData\Roaming\npm\claude.cmd" — would
// be split by cmd.exe and fail. Quote it defensively. We only do this for the
// boolean (cmd.exe) shell path; an explicit shell string carries its own
// quoting rules and is the caller's responsibility.
if (
isWindows() &&
spawnShell === true &&
/\s/.test(spawnCommand) &&
!spawnCommand.startsWith('"')
) {
spawnCommand = `"${spawnCommand}"`;
}
// When spawning through the default Windows shell (cmd.exe via ComSpec),
// Node concatenates the command and args into a single command line without
// quoting the command itself. A command path that contains spaces — e.g. an
// npm shim under "C:\Users\First Last\AppData\Roaming\npm\claude.cmd" — would
// be split by cmd.exe and fail. Quote it defensively. We only do this for the
// boolean (cmd.exe) shell path; an explicit shell string carries its own
// quoting rules and is the caller's responsibility.
const shellBaseName =
typeof spawnShell === 'string'
? spawnShell.split(/[\\/]/).pop()?.toLowerCase()
: undefined;
if (
isWindows() &&
(spawnShell === true || shellBaseName === 'cmd.exe' || shellBaseName === 'cmd') &&
/\s/.test(spawnCommand) &&
!spawnCommand.startsWith('"')
) {
spawnCommand = `"${spawnCommand}"`;
}
🤖 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 `@src/main/process-manager/spawners/ChildProcessSpawner.ts` around lines 329 -
343, The current quote-guard only runs when spawnShell === true and misses cases
where spawnShell is a concrete shell string that points to cmd.exe; update the
condition in ChildProcessSpawner around spawnCommand quoting to also detect
explicit cmd.exe shell strings (e.g., check spawnShell === true OR
basename/spawnShell endsWith 'cmd.exe' case-insensitively) so that when running
on Windows and the resolved shell is cmd.exe you still wrap spawnCommand with
quotes if it contains whitespace and isn't already quoted; refer to symbols
spawnShell, spawnCommand and isWindows() when making this change.

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