Skip to content

[codex] Fix full disk access detection#4825

Merged
Kitenite merged 1 commit into
mainfrom
fix-disk-access-detection
May 23, 2026
Merged

[codex] Fix full disk access detection#4825
Kitenite merged 1 commit into
mainfrom
fix-disk-access-detection

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 21, 2026

Summary

Fixes the macOS Full Disk Access detector used by the desktop permissions router.

The previous implementation probed only ~/Library/Safari/Bookmarks.plist. That file is TCC-protected, but it is also optional: users with no Safari bookmarks may not have it at all. In that case Superset reported Full Disk Access as missing even after the user granted it in System Settings.

This PR moves FDA detection into a small helper that probes multiple protected user-data files. Missing probe files are skipped, while actual open failures such as EPERM or EACCES still report FDA as unavailable. It also extracts the other native permission checks/request entry points into a tested helper so the settings deep links and Microphone grant fallback behavior are covered.

Impact

Users who have granted Full Disk Access should no longer get stuck in onboarding or permissions settings just because Safari has no bookmarks file.

Validation

  • bun test src/lib/trpc/routers/permissions/full-disk-access.test.ts src/lib/trpc/routers/permissions/native-permissions.test.ts (15 tests covering FDA probe behavior, real fs.openSync fallback, Accessibility status, Microphone granted/denied/error branches, and all permission settings request URLs)
  • Real Bun runtime call against this machine's macOS protected files returned {"fullDiskAccess":true}
  • Real Electron runtime call returned {"name":"Electron","fullDiskAccess":true,"accessibility":true,"microphone":"granted"}
  • Fresh temporary Electron app bundle with a new bundle id launched through LaunchServices returned {"fullDiskAccess":false,"accessibility":false,"microphone":"not-determined"}, confirming denied/not-yet-granted TCC paths
  • Automation and Local Network currently have request deep links only; there is no app status reader for those permissions to verify after granting
  • bun run --cwd apps/desktop typecheck
  • bun run lint:fix
  • bun run lint

Summary by CodeRabbit

  • New Features

    • Added macOS checks and user flows for Full Disk Access, Accessibility, Microphone, Apple Events, and Local Network permissions.
  • Refactor

    • Unified permission status checks and request actions into shared native helpers for consistent behavior.
  • Tests

    • Added thorough tests for Full Disk Access probing and for native permission request flows, including error and edge-case handling.

Review Change Stack

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 21, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 21, 2026

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Implement robust Full Disk Access probe logic
2 Consolidate native permission helpers and settings links
3 Refactor permissions router to use native helpers
Open in Stage

Chapters generated by Stage for commit 9b511bf on May 22, 2026 12:19am UTC.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d482c14c-3e7d-4536-bce0-0f5b41186ee7

📥 Commits

Reviewing files that changed from the base of the PR and between 1915468 and 9b511bf.

📒 Files selected for processing (5)
  • apps/desktop/src/lib/trpc/routers/permissions.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts
  • apps/desktop/src/lib/trpc/routers/permissions/native-permissions.test.ts
  • apps/desktop/src/lib/trpc/routers/permissions/native-permissions.ts

📝 Walkthrough

Walkthrough

Extracts Full Disk Access probing into a utility, implements a native-permissions module that aggregates permission status checks and request helpers (including microphone askForMediaAccess behavior), adds tests for both utilities, and updates the permissions router to delegate status and request operations to the new helpers.

Changes

Permissions refactor & native helpers

Layer / File(s) Summary
Full Disk Access Utility Implementation
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts
Defines macOS probe paths, a default synchronous open/close read probe, skippable error classification, getFullDiskAccessProbePaths for home-directory expansion, and checkFullDiskAccess({ homeDirectory?, readProbe? }) that returns true on the first successful probe, continues on ENOENT/ENOTDIR, and returns false immediately on other errors.
Full Disk Access Test Suite
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts
Bun tests inject a readProbe and synthesize errors with .code to validate: TCC success probes only TCC; optional probe ENOENT/ENOTDIR continues; EPERM/EACCES stops with false; all ENOENT returns false; plus an integration-style test using a temporary home dir and real Messages/chat.db.
Native permissions implementation
apps/desktop/src/lib/trpc/routers/permissions/native-permissions.ts
Adds PERMISSION_SETTINGS_URLS and helpers: checkAccessibility, checkMicrophone, getPermissionStatus() (aggregates fullDiskAccess/accessibility/microphone), and async requestFullDiskAccess, requestAccessibility, requestMicrophone (tries askForMediaAccess("microphone") then falls back to opening settings), requestAppleEvents, requestLocalNetwork. All support optional API injection for testing.
Native permissions test suite
apps/desktop/src/lib/trpc/routers/permissions/native-permissions.test.ts
Mocks Electron shell.openExternal and systemPreferences APIs, validates accessibility and microphone status checks, verifies request helpers open correct settings URLs, and checks requestMicrophone returned { granted: true } when the native prompt grants access and { granted: false } while opening settings otherwise.
Permissions Router Integration
apps/desktop/src/lib/trpc/routers/permissions.ts
Router imports getPermissionStatus and request* helpers from ./permissions/native-permissions and delegates getStatus and request mutations to them, removing previous inline permission-check and request logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • jordanmiguel

Poem

🐰 I hopped through paths both hidden and deep,
probing TCC where secrets sleep.
A temp chat file showed me the gate,
ENOENT let me skip and wait.
EPERM stopped me — so I nibble and keep.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: fixing Full Disk Access detection on macOS, which aligns with the main refactoring to probe multiple protected files instead of just Safari Bookmarks.
Description check ✅ Passed The description includes a clear summary of the problem and solution, related impact, comprehensive validation details, and covers all template sections appropriately.
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-disk-access-detection

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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
Contributor

greptile-apps Bot commented May 21, 2026

Greptile Summary

This PR replaces a single-file TCC probe (Safari/Bookmarks.plist) with a helper that tries four macOS-protected paths in sequence, skipping files that don't exist and returning false only on a genuine permission denial or when no probe file is present at all.

  • The core logic in full-disk-access.ts is sound: ENOENT causes a fallthrough to the next probe, any other error immediately returns false, and an injectable readProbe dependency makes the unit tests clean and deterministic.
  • TCC.db is placed first in the probe list, which is the most reliable signal since macOS manages that file directly as part of TCC, making it the best early-exit candidate.
  • Non-ENOENT/non-permission error codes (EMFILE, EIO, ENOTDIR) are currently treated as FDA denied rather than skipped, which could produce spurious false negatives under unusual system conditions.

Confidence Score: 4/5

Safe to merge; the multi-probe approach is a clear improvement and the only theoretical concern is a narrow class of non-permission OS errors being misclassified.

The change is well-structured and the test suite covers all four key code paths. The one concern — transient system errors like EMFILE or EIO being treated as "FDA denied" — requires an unlikely combination of conditions and would not cause data loss, only a misleading permissions prompt.

apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts — specifically the isFileNotFoundError guard and how non-ENOENT, non-permission errors are handled.

Important Files Changed

Filename Overview
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts New FDA detection module that probes multiple TCC-protected files; logic is sound but non-ENOENT/non-permission errors (EIO, EMFILE, ENOTDIR) are silently mapped to "FDA denied".
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts Comprehensive unit tests covering the four main code paths (success, ENOENT fallthrough, permission error, all absent); no issues found.
apps/desktop/src/lib/trpc/routers/permissions.ts Cleanly removes the inline single-probe implementation and delegates to the new module; no logic changes to the router itself.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[checkFullDiskAccess called] --> B[Build probe path list from homeDirectory]
    B --> C{More probe paths?}
    C -- Yes --> D[openForRead probePath]
    D -- success --> E[return true FDA granted]
    D -- throws ENOENT --> C
    D -- throws other error e.g. EPERM EACCES --> F[return false FDA denied]
    C -- No more paths --> G[return false no probe file found]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts:19-25
Non-filesystem errors such as `EMFILE` (too many open file descriptors), `EIO` (I/O error), or `ENOTDIR` (a path component is not a directory) would fall through to `return false` here, falsely reporting that FDA is unavailable. These are transient system-resource or path-resolution failures, not TCC denials. Treating them as permission errors can strand users in the permissions onboarding screen for no real reason.

```suggestion
function isSkippableError(error: unknown): boolean {
	if (!(error instanceof Error) || !("code" in error)) {
		return false;
	}
	const code = (error as NodeJS.ErrnoException).code;
	// ENOENT  – probe file genuinely absent, try the next one.
	// ENOTDIR – a path component is missing (e.g. ~/Library/Safari/ never created).
	return code === "ENOENT" || code === "ENOTDIR";
}
```

Reviews (1): Last reviewed commit: "Fix full disk access detection" | Re-trigger Greptile

Comment on lines +19 to +25
function isFileNotFoundError(error: unknown): boolean {
return (
error instanceof Error &&
"code" in error &&
(error as NodeJS.ErrnoException).code === "ENOENT"
);
}
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.

P2 Non-filesystem errors such as EMFILE (too many open file descriptors), EIO (I/O error), or ENOTDIR (a path component is not a directory) would fall through to return false here, falsely reporting that FDA is unavailable. These are transient system-resource or path-resolution failures, not TCC denials. Treating them as permission errors can strand users in the permissions onboarding screen for no real reason.

Suggested change
function isFileNotFoundError(error: unknown): boolean {
return (
error instanceof Error &&
"code" in error &&
(error as NodeJS.ErrnoException).code === "ENOENT"
);
}
function isSkippableError(error: unknown): boolean {
if (!(error instanceof Error) || !("code" in error)) {
return false;
}
const code = (error as NodeJS.ErrnoException).code;
// ENOENT – probe file genuinely absent, try the next one.
// ENOTDIR – a path component is missing (e.g. ~/Library/Safari/ never created).
return code === "ENOENT" || code === "ENOTDIR";
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts
Line: 19-25

Comment:
Non-filesystem errors such as `EMFILE` (too many open file descriptors), `EIO` (I/O error), or `ENOTDIR` (a path component is not a directory) would fall through to `return false` here, falsely reporting that FDA is unavailable. These are transient system-resource or path-resolution failures, not TCC denials. Treating them as permission errors can strand users in the permissions onboarding screen for no real reason.

```suggestion
function isSkippableError(error: unknown): boolean {
	if (!(error instanceof Error) || !("code" in error)) {
		return false;
	}
	const code = (error as NodeJS.ErrnoException).code;
	// ENOENT  – probe file genuinely absent, try the next one.
	// ENOTDIR – a path component is missing (e.g. ~/Library/Safari/ never created).
	return code === "ENOENT" || code === "ENOTDIR";
}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Re-trigger cubic

@Kitenite Kitenite force-pushed the fix-disk-access-detection branch from c1e0eeb to 690ddcc Compare May 21, 2026 22:47
Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts (1)

102-115: ⚡ Quick win

Add an explicit EACCES permission-denied test.

You already pin EPERM; adding EACCES closes the permission-denied regression gap for this helper’s contract.

Proposed test addition
@@
 	it("returns false when an existing protected file cannot be opened", () => {
 		const openedPaths: string[] = [];
@@
 		expect(hasFullDiskAccess).toBe(false);
 		expect(openedPaths).toEqual([tccDatabasePath]);
 	});
+
+	it("returns false when an existing protected file is not readable (EACCES)", () => {
+		const openedPaths: string[] = [];
+
+		const hasFullDiskAccess = checkFullDiskAccess({
+			homeDirectory,
+			readProbe: (filePath) => {
+				openedPaths.push(filePath);
+				throw createFileSystemError("EACCES");
+			},
+		});
+
+		expect(hasFullDiskAccess).toBe(false);
+		expect(openedPaths).toEqual([tccDatabasePath]);
+	});
🤖 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/permissions/full-disk-access.test.ts`
around lines 102 - 115, Add a new test alongside the existing "returns false
when an existing protected file cannot be opened" case that verifies EACCES is
treated the same as EPERM: call checkFullDiskAccess with the same homeDirectory
and a readProbe that pushes filePath to openedPaths and throws
createFileSystemError("EACCES"), then assert the returned value is false and
openedPaths equals [tccDatabasePath]; this ensures checkFullDiskAccess (and its
readProbe handling) explicitly covers the EACCES permission-denied variant.
🤖 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/permissions/full-disk-access.test.ts`:
- Around line 102-115: Add a new test alongside the existing "returns false when
an existing protected file cannot be opened" case that verifies EACCES is
treated the same as EPERM: call checkFullDiskAccess with the same homeDirectory
and a readProbe that pushes filePath to openedPaths and throws
createFileSystemError("EACCES"), then assert the returned value is false and
openedPaths equals [tccDatabasePath]; this ensures checkFullDiskAccess (and its
readProbe handling) explicitly covers the EACCES permission-denied variant.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c31131a9-0e30-470b-9b44-c9c4033cfec8

📥 Commits

Reviewing files that changed from the base of the PR and between c1e0eeb and 690ddcc.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/permissions.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts

@Kitenite Kitenite force-pushed the fix-disk-access-detection branch from 690ddcc to 8994883 Compare May 21, 2026 23:35
Copy link
Copy Markdown
Contributor

@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 `@apps/desktop/src/lib/trpc/routers/permissions/native-permissions.test.ts`:
- Around line 95-107: The test "returns granted when the native Microphone
prompt grants access" assumes macOS behavior but doesn't guard for platform;
update the test to run platform-aware by checking process.platform (or
skip/assert different expected behavior when not "darwin"). Specifically, in the
test that calls requestMicrophone with a stubbed
systemPreferencesApi.askForMediaAccess and inspects openedUrls and result,
conditionally assert { granted: true } and openedUrls === [] only when
process.platform === "darwin"; otherwise assert that
PERMISSION_SETTINGS_URLS.microphone was opened via shellApi (openedUrls contains
PERMISSION_SETTINGS_URLS.microphone) and result is { granted: false }. Reference
requestMicrophone, systemPreferencesApi.askForMediaAccess,
PERMISSION_SETTINGS_URLS.microphone, openedUrls, and shellApi when making the
conditional assertions.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 081c2f30-2396-4382-b1ad-468b53daf609

📥 Commits

Reviewing files that changed from the base of the PR and between 690ddcc and 8994883.

📒 Files selected for processing (5)
  • apps/desktop/src/lib/trpc/routers/permissions.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.test.ts
  • apps/desktop/src/lib/trpc/routers/permissions/full-disk-access.ts
  • apps/desktop/src/lib/trpc/routers/permissions/native-permissions.test.ts
  • apps/desktop/src/lib/trpc/routers/permissions/native-permissions.ts

@Kitenite Kitenite force-pushed the fix-disk-access-detection branch from 8994883 to 1915468 Compare May 22, 2026 00:10
@Kitenite Kitenite force-pushed the fix-disk-access-detection branch from 1915468 to 9b511bf Compare May 22, 2026 00:19
@Kitenite Kitenite merged commit e065362 into main May 23, 2026
9 checks passed
@Kitenite Kitenite deleted the fix-disk-access-detection branch May 23, 2026 07:13
@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