Skip to content

fix: silence main-process Sentry noise from FS watchers#879

Merged
pedramamini merged 1 commit intomainfrom
fix/sentry-main-process-noise
Apr 28, 2026
Merged

fix: silence main-process Sentry noise from FS watchers#879
pedramamini merged 1 commit intomainfrom
fix/sentry-main-process-noise

Conversation

@pedramamini
Copy link
Copy Markdown
Collaborator

@pedramamini pedramamini commented Apr 21, 2026

Summary

Triaged the top unresolved main-process Sentry issues and landed fixes for the two that are real bugs on main.

  • MAESTRO-9A — unhandled rejections from fs.watch() (14 events; "UNKNOWN: unknown error, watch" on Windows). history-manager.ts and marketplace.ts created watchers without .on('error'), so transient errors escaped as unhandled rejections.
  • MAESTRO-G5/G6EBUSY: resource busy or locked, lstat 'C:\DumpStack.log.tmp' / hiberfil.sys (1,279 events). Users who point a watcher at or near a drive root hit always-locked Windows system files during chokidar's initial walk.

Three other top issues were ruled out (noted in commit body): MAESTRO-87 is from a fork ("semantica" v0.56.1), MAESTRO-B9 is already fixed by 70332d993 (MAX_SESSION_FILE_SIZE guard), and MAESTRO-FM lives on the rc branch only.

Changes

  • New src/main/utils/watcher-ignore.ts exporting a shared WINDOWS_LOCKED_SYSTEM_FILES regex (pagefile.sys, hiberfil.sys, swapfile.sys, DumpStack.log[.tmp], System Volume Information).
  • Wired the shared ignore into the three recursive chokidar watchers: documentGraph, autorun, and git:watchWorktreeDirectory.
  • history-manager.ts and marketplace.ts: added try/catch around fs.watch() creation plus an .on('error', ...) handler that logs via logger.warn instead of crashing.
  • Sentry beforeSend filter in src/main/index.ts drops any residual EBUSY/EPERM lstat events targeting the locked paths as defense in depth.
  • Tests: extended history-manager.test.ts for the new error handler and fs.watch-throws fallback; updated the existing FSWatcher mocks to expose on; relaxed the git:watchWorktreeDirectory ignored-array assertion.

Test plan

  • npx tsc --noEmit — no new errors
  • npx vitest run src/__tests__/main/ipc/handlers/git.test.ts — passes
  • Pre-push hook full test suite — passes (was failing on the git-watcher assertion before the test update)
  • Manual: point Auto Run at C:\ on a Windows box and confirm no EBUSY noise lands in Sentry on the RC channel.

Summary by CodeRabbit

  • Bug Fixes

    • File watchers now handle errors gracefully instead of causing crashes
    • Unhandled errors from filesystem watchers are properly caught and logged
    • Windows system-locked files no longer trigger watcher failures
  • Improvements

    • Crash reports are now filtered to exclude expected Windows filesystem errors

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

📝 Walkthrough

Walkthrough

The changes introduce a centralized Windows system file ignore pattern for filesystem watchers and improve error handling across multiple watcher implementations to prevent unhandled rejections on Windows systems.

Changes

Cohort / File(s) Summary
Windows System Files Utility
src/main/utils/watcher-ignore.ts
New module exporting WINDOWS_LOCKED_SYSTEM_FILES regex constant to match Windows drive-root system files (pagefile.sys, hiberfil.sys, swapfile.sys, DumpStack.log, System Volume Information) that fail filesystem operations with EBUSY/EPERM.
Watcher Error Handling
src/main/history-manager.ts, src/main/ipc/handlers/marketplace.ts
Added try/catch and error event listeners to fs.watch and chokidar watchers to catch runtime failures, log warnings, and prevent unhandled rejections.
Watcher Ignore Pattern Updates
src/main/ipc/handlers/autorun.ts, src/main/ipc/handlers/documentGraph.ts, src/main/ipc/handlers/git.ts
Extended chokidar.watch ignored configuration to include WINDOWS_LOCKED_SYSTEM_FILES pattern alongside existing dotfile ignore rules across three IPC handlers.
Sentry Event Filtering
src/main/index.ts
Added filtering logic in Sentry beforeSend hook to drop events for Windows filesystem lstat failures (EBUSY/EPERM) on locked system files to reduce noise in crash reporting.
Test Updates
src/__tests__/main/history-manager.test.ts, src/__tests__/main/ipc/handlers/git.test.ts
Updated test mocks and assertions: added error handler tests for watchers, verified warning logging on watch failures, and updated ignored option assertion to expect array format.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

ready to merge

Poem

🐰 A watcher's song on Windows' ground,
Where locked files guard with EBUSY sound—
We catch their errors, gentle and kind,
Ignoring the files we'll never find.
No more rejections left behind! 🎯

🚥 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 and concisely summarizes the main objective: silencing Sentry noise from filesystem watchers in the main process.
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.

✏️ 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-main-process-noise

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 Apr 21, 2026

Greptile Summary

This PR fixes two classes of Windows-only Sentry noise: unhandled rejections from fs.watch() in history-manager.ts and marketplace.ts (missing .on('error') handlers), and EBUSY spam when chokidar walks Windows drive roots containing always-locked system files (pagefile.sys, hiberfil.sys, etc.). The fix introduces a shared WINDOWS_LOCKED_SYSTEM_FILES regex wired into three chokidar watchers, adds synchronous try/catch around fs.watch() creation, and adds a beforeSend Sentry filter as defense-in-depth.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style suggestions with no functional impact.

No P0 or P1 issues found. The regex is correct (case-insensitive, handles both / and \ separators, covers all documented locked filenames). Error handlers and try/catch patterns are logically sound. Tests cover the new code paths. The only findings are minor: a missing inline comment on the regex and a redundant ignore entry on the depth-0 git watcher.

No files require special attention.

Important Files Changed

Filename Overview
src/main/utils/watcher-ignore.ts New shared regex for Windows locked system files; pattern and flags (case-insensitive, path-separator-aware) are correct.
src/main/history-manager.ts try/catch wraps fs.watch() creation and .on('error') handler added; error state resets watcher to null correctly on synchronous throw.
src/main/index.ts beforeSend filter drops EBUSY/EPERM lstat events for known Windows system files as defense-in-depth; regex anchoring and file list match watcher-ignore.ts.
src/main/ipc/handlers/marketplace.ts Added .on('error') handler to localManifestWatcher to prevent unhandled rejections from transient Windows fs errors.
src/main/ipc/handlers/autorun.ts WINDOWS_LOCKED_SYSTEM_FILES added to chokidar ignored array; correct use of array form alongside existing dotfile regex.
src/main/ipc/handlers/documentGraph.ts WINDOWS_LOCKED_SYSTEM_FILES added to chokidar ignored list alongside existing node_modules/dist/build/.git patterns.
src/main/ipc/handlers/git.ts WINDOWS_LOCKED_SYSTEM_FILES added to the depth:0 worktree watcher — correct syntactically but the watcher never reaches a drive root, making the ignore academically redundant.
src/tests/main/history-manager.test.ts Mock FSWatcher updated with on: vi.fn() across all existing tests; new tests cover error handler registration and synchronous throw fallback.
src/tests/main/ipc/handlers/git.test.ts Assertion on ignored array relaxed to expect.any(RegExp) for the second entry, accommodating the new WINDOWS_LOCKED_SYSTEM_FILES import.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Chokidar / fs.watch starts] --> B{Path contains locked\nWindows system file?}
    B -- Yes --> C[WINDOWS_LOCKED_SYSTEM_FILES\nregex matches → skip]
    B -- No --> D[Watch proceeds normally]

    D --> E{Runtime error emitted\ne.g. EBUSY / UNKNOWN}
    E -- fs.watch --> F[.on error handler\nlogs warn, swallows]
    E -- chokidar --> G{Error matches lstat\n+ system filename?}
    G -- Yes --> H[Sentry beforeSend\nreturns null → dropped]
    G -- No --> I[Sent to Sentry normally]

    C --> J[No EBUSY noise]
    F --> J
    H --> J
Loading

Reviews (1): Last reviewed commit: "test: update git watcher assertion for i..." | Re-trigger Greptile


/** Windows drive-root system files that always fail lstat with EBUSY. */
export const WINDOWS_LOCKED_SYSTEM_FILES: RegExp = new RegExp(
'(^|[/\\\\])(pagefile\\.sys|hiberfil\\.sys|swapfile\\.sys|DumpStack\\.log(\\.tmp)?|System Volume Information)$',
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 System Volume Information not covered by dotfile ignore, but matched correctly

Minor readability note: System Volume Information contains a space and is not a file extension pattern, so it stands out from the other entries. A brief inline comment on that entry would help future readers understand why it's included alongside .sys files. The regex itself is correct.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines 1224 to +1230

// Start watching the directory (only top level, not recursive)
const watcher = chokidar.watch(worktreePath, {
ignored: /(^|[/\\])\../, // Ignore dotfiles
ignored: [
/(^|[/\\])\../, // Ignore dotfiles
WINDOWS_LOCKED_SYSTEM_FILES,
],
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 WINDOWS_LOCKED_SYSTEM_FILES applied to a depth: 0, non-drive-root watcher

The git worktree watcher is configured with depth: 0 and always targets a specific worktree directory (never a Windows drive root), so it can't actually encounter pagefile.sys/hiberfil.sys during its walk. Adding the ignore is harmless, but it inflates the ignored option unnecessarily. If someone later audits why locked-file ignores appear on a Git worktree watcher, it may cause confusion. Consider keeping it consistent with other chokidar watchers only if there's a real EBUSY risk at the worktree path.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

🧹 Nitpick comments (2)
src/main/history-manager.ts (1)

496-516: LGTM — scoped try/catch + 'error' listener addresses the Windows watcher noise.

Wrapping fs.watch() and installing an 'error' listener matches the pattern used in marketplace.ts and directly targets the MAESTRO-9A unhandled-rejection class. Setting this.watcher = null in the catch branch correctly preserves the "not watching" invariant so stopWatching() remains a no-op and a later startWatching() call will retry.

Minor note (non-blocking): fs.mkdirSync on line 493 is still outside the try/catch. A Windows directory-creation failure (e.g., EPERM on a locked/virtualized userData path) would propagate as before. If you want full symmetry with the "don't let watcher setup crash the app" goal, consider moving the mkdirSync call inside the same try block. Leaving as-is is also defensible since an unwritable userData dir is a genuinely unexpected condition worth surfacing.

♻️ Optional: include mkdirSync in the guarded block
-		// Ensure directory exists before watching
-		if (!fs.existsSync(this.historyDir)) {
-			fs.mkdirSync(this.historyDir, { recursive: true });
-		}
-
 		try {
+			// Ensure directory exists before watching
+			if (!fs.existsSync(this.historyDir)) {
+				fs.mkdirSync(this.historyDir, { recursive: true });
+			}
+
 			this.watcher = fs.watch(this.historyDir, (_eventType, filename) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/history-manager.ts` around lines 496 - 516, The mkdirSync that
creates the historyDir is outside the guarded start-up block, so a
Windows/permission error from fs.mkdirSync can still crash startup; move the
fs.mkdirSync(...) call into the same try block that creates and assigns
this.watcher (the block that calls fs.watch and sets this.watcher and installs
the 'error' listener) so mkdir failures are caught, logged by the existing catch
which sets this.watcher = null, and do not produce an unhandled exception—adjust
the startWatching/init logic around fs.mkdirSync and this.watcher accordingly.
src/__tests__/main/history-manager.test.ts (1)

1288-1310: LGTM — new tests cover both watcher-error paths.

The two added tests correctly exercise (1) watcher.on('error', ...) registration and (2) the synchronous fs.watch throw → logger.warn + no rethrow behavior added in startWatching(). Mock updates to include on: vi.fn() across existing tests are consistent.

One optional tightening: in the "fs.watch throws" test you could also assert that a subsequent manager.stopWatching() is safe to call (since this.watcher is set to null in the catch branch) and that a later manager.startWatching(...) can recover by re-invoking fs.watch. Not required — the existing "safe to call stopWatching when not watching" test already covers the null case indirectly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/main/history-manager.test.ts` around lines 1288 - 1310, Add
assertions to the "fs.watch itself throws" test to verify recovery: after
mocking fs.watch to throw and calling manager.startWatching(...), call
manager.stopWatching() and assert it does not throw (ensuring this.watcher is
null/safe), then set mockWatch to a successful watcher mock and call
manager.startWatching(...) again and assert mockWatch was invoked (or
watcher.on('error') was registered) to confirm recovery; reference
manager.startWatching, manager.stopWatching, and mockWatch in the changes.
🤖 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/main/index.ts`:
- Around line 217-220: Replace the loose substring check against exceptionValue
with logic that extracts the lstat path from the existing regex (the current
/^(EBUSY|EPERM): [^,]+, lstat /i.test(exceptionValue) usage), then test that
extracted path against the shared WINDOWS_LOCKED_SYSTEM_FILES matcher instead of
scanning the whole exception string; use the same extraction and test so false
positives like "my-pagefile.sys.bak" are not matched and the rule stays
consistent with the watcher ignore rule.

---

Nitpick comments:
In `@src/__tests__/main/history-manager.test.ts`:
- Around line 1288-1310: Add assertions to the "fs.watch itself throws" test to
verify recovery: after mocking fs.watch to throw and calling
manager.startWatching(...), call manager.stopWatching() and assert it does not
throw (ensuring this.watcher is null/safe), then set mockWatch to a successful
watcher mock and call manager.startWatching(...) again and assert mockWatch was
invoked (or watcher.on('error') was registered) to confirm recovery; reference
manager.startWatching, manager.stopWatching, and mockWatch in the changes.

In `@src/main/history-manager.ts`:
- Around line 496-516: The mkdirSync that creates the historyDir is outside the
guarded start-up block, so a Windows/permission error from fs.mkdirSync can
still crash startup; move the fs.mkdirSync(...) call into the same try block
that creates and assigns this.watcher (the block that calls fs.watch and sets
this.watcher and installs the 'error' listener) so mkdir failures are caught,
logged by the existing catch which sets this.watcher = null, and do not produce
an unhandled exception—adjust the startWatching/init logic around fs.mkdirSync
and this.watcher accordingly.
🪄 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: 335b77a5-77ac-4c9c-aa65-05ccc0ce5a0f

📥 Commits

Reviewing files that changed from the base of the PR and between 72c67e4 and 72edc25.

📒 Files selected for processing (9)
  • src/__tests__/main/history-manager.test.ts
  • src/__tests__/main/ipc/handlers/git.test.ts
  • src/main/history-manager.ts
  • src/main/index.ts
  • src/main/ipc/handlers/autorun.ts
  • src/main/ipc/handlers/documentGraph.ts
  • src/main/ipc/handlers/git.ts
  • src/main/ipc/handlers/marketplace.ts
  • src/main/utils/watcher-ignore.ts

Comment thread src/main/index.ts
Comment on lines +217 to +220
/^(EBUSY|EPERM): [^,]+, lstat /i.test(exceptionValue) &&
/(pagefile\.sys|hiberfil\.sys|swapfile\.sys|DumpStack\.log|System Volume Information)/i.test(
exceptionValue
)
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

Reuse the shared locked-file matcher here.

Line 218 scans the whole exception string with loose substrings, so real EBUSY/EPERM lstat failures for paths like my-pagefile.sys.bak can be dropped from Sentry. Extract the lstat path and apply WINDOWS_LOCKED_SYSTEM_FILES to avoid drift with the watcher ignore rule.

♻️ Proposed fix
 import { WakaTimeManager } from './wakatime-manager';
+import { WINDOWS_LOCKED_SYSTEM_FILES } from './utils/watcher-ignore';
-					if (
-						/^(EBUSY|EPERM): [^,]+, lstat /i.test(exceptionValue) &&
-						/(pagefile\.sys|hiberfil\.sys|swapfile\.sys|DumpStack\.log|System Volume Information)/i.test(
-							exceptionValue
-						)
-					) {
+					const lockedSystemFileLstat = exceptionValue.match(
+						/^(?:EBUSY|EPERM): [^,]+, lstat ['"]?(.+?)['"]?$/i
+					);
+					const lstatPath = lockedSystemFileLstat?.[1]?.replace(/[\\/]+$/, '');
+					if (lstatPath && WINDOWS_LOCKED_SYSTEM_FILES.test(lstatPath)) {
 						return null;
 					}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/index.ts` around lines 217 - 220, Replace the loose substring check
against exceptionValue with logic that extracts the lstat path from the existing
regex (the current /^(EBUSY|EPERM): [^,]+, lstat /i.test(exceptionValue) usage),
then test that extracted path against the shared WINDOWS_LOCKED_SYSTEM_FILES
matcher instead of scanning the whole exception string; use the same extraction
and test so false positives like "my-pagefile.sys.bak" are not matched and the
rule stays consistent with the watcher ignore rule.

@pedramamini pedramamini merged commit bd7e9d8 into main Apr 28, 2026
5 checks passed
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