Skip to content

fix(fs): exists rethrows Deno.errors.PermissionDenied when existence is indeterminate#7170

Open
LeSingh1 wants to merge 1 commit into
denoland:mainfrom
LeSingh1:fs-exists-throw-permission-denied
Open

fix(fs): exists rethrows Deno.errors.PermissionDenied when existence is indeterminate#7170
LeSingh1 wants to merge 1 commit into
denoland:mainfrom
LeSingh1:fs-exists-throw-permission-denied

Conversation

@LeSingh1
Copy link
Copy Markdown

Fixes #6528.

exists and existsSync returned true for any path that produced a PermissionDenied at stat() time (with --allow-read granted), on the assumption that the OS error meant the item is there, just not readable. The far more common cause is that the parent directory isn't traversable, in which case we genuinely cannot determine existence and the true answer silently misleads the caller.

Repro from the issue:

$ ls /root
ls: cannot open directory '/root': Permission denied
$ cat test.ts
import { exists } from "jsr:@std/fs/exists";
console.log(await exists("/root/foo"));
$ deno run --allow-read test.ts
true

After the patch, exists("/root/foo") rethrows Deno.errors.PermissionDenied and the caller can pick the right policy for their context.

exists(path, { isReadable: true }) is unchanged. That question has a defensible answer (the OS just told us "can't read" → false), and the existing exists() returns true for an existing dir symlink test in fs/exists_test.ts relies on it (it chmod-0o000s the parent and expects false).

Two new Unix-gated regression tests cover both exists() and existsSync(), asserting:

  • Deno.errors.PermissionDenied is rethrown when the parent is unreadable
  • { isReadable: true } still returns false in the same scenario

This is a small but real behavior change for callers that today swallow a misleading true. Net diff is small and is documented inline at both call sites.

exists/existsSync returned `true` for any path that produced a
PermissionDenied at stat() time (with --allow-read granted), on the
assumption that the OS error meant "the item is there, just not
readable". The far more common cause is that the parent directory
isn't traversable, in which case we can't determine existence and the
"true" answer silently misleads callers (denoland#6528).

Rethrow Deno.errors.PermissionDenied for the no-isReadable path so
callers can decide how to handle the indeterminate case. Keep the old
behavior for `isReadable: true`: that question always has a
defensible answer (the OS just told us "can't read" → false). This
preserves the existing `exists() returns true for an existing dir
symlink` test that exercises the isReadable branch with chmod 000 on
the parent.

Adds Unix-gated regression tests for both exists() and existsSync()
covering the chmod-000-parent case and confirming `isReadable: true`
still returns false.

Fixes denoland#6528
@github-actions github-actions Bot added the fs label May 30, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.57%. Comparing base (cdf74a8) to head (7d1e8db).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7170      +/-   ##
==========================================
- Coverage   94.57%   94.57%   -0.01%     
==========================================
  Files         636      636              
  Lines       52142    52146       +4     
  Branches     9401     9403       +2     
==========================================
+ Hits        49315    49318       +3     
  Misses       2249     2249              
- Partials      578      579       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@std/fs/exists returns true when a path is inaccessible.

1 participant