Skip to content

fix(core): classify EACCES as permission_denied, not auth_failed - #124

Merged
Hydralerne merged 1 commit into
oblien:mainfrom
shuvamk:fix/core-eacces-permission-denied
Jul 22, 2026
Merged

fix(core): classify EACCES as permission_denied, not auth_failed#124
Hydralerne merged 1 commit into
oblien:mainfrom
shuvamk:fix/core-eacces-permission-denied

Conversation

@shuvamk

@shuvamk shuvamk commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem

MESSAGE_PATTERNS in packages/core/src/connectivity.ts is documented as "Ordered most-specific → least", but the auth rule carries a bare permission denied token and sits first:

[/ |permission denied|publickey|authentication failed|/i, "auth_failed"],

[/eacces||access denied|forbidden|read-only|not writable/i, "permission_denied"],

Node spells a filesystem permission error as EACCES: permission denied, <syscall> '<path>' — which contains permission denied, so it matches the auth rule first.

Consequences:

  1. Every EACCES is classified auth_failed.
  2. The eacces alternative in the permission_denied rule is unreachable for real Node fs errors.
  3. permission_denied — documented as "authed, but not allowed to do the thing (e.g. write path)" — never fires for the exact case its comment names.

Executed on main:

EACCES: permission denied, open '/backups/openship/db.sql'  -> auth_failed        ✗
EACCES: permission denied, mkdir '/mnt/backup/nightly'      -> auth_failed        ✗
EROFS: read-only file system, open '/srv/backups/x'         -> permission_denied  ✓
Error: access denied writing to /srv/backups                -> permission_denied  ✓

So two ways of saying the same thing already disagree today.

Impact

ConnectivityCode exists so "UIs can map copy once", per the type's own comment — the code drives what the operator is told. An operator whose backup destination directory isn't writable gets "credentials rejected" and goes off rotating SSH keys or re-entering a password, when the actual fix is chown/chmod on the destination path.

Fix

Match the errno tokens ahead of the auth rule:

[/eacces|erofs/i, "permission_denied"],

EACCES / EROFS never appear in SSH's Permission denied (publickey) or Permission denied, please try again, so the errno rule can't swallow genuine auth failures. The auth rule keeps its permission denied token for those.

Tests

Two cases added to the existing packages/core/test/connectivity.test.ts (@repo/core 101 → 103):

  • permission_denied for EACCES (open + mkdir) and EROFS
  • auth_failed still for Permission denied (publickey) and Permission denied, please try again — pinning the boundary so the new rule can't widen later

Verified against the unfixed source: the filesystem case fails, the SSH guard passes either way. All pre-existing expectations in that file are untouched.

Full suite: bun run test5/5 turbo tasks successful (api 714, adapters 240, core 103, db 25, dashboard 17).

MESSAGE_PATTERNS is documented as ordered most-specific → least, but the
auth rule carries a bare `permission denied` token and sits first. Node
spells a filesystem permission error as

  EACCES: permission denied, open '/backups/openship/db.sql'

so every EACCES matches the auth rule and is reported as auth_failed. The
`eacces` alternative in the later permission_denied rule is unreachable,
and the code documented as "authed, but not allowed to do the thing (e.g.
write path)" never fires for the exact case it names.

Two ways of saying the same thing already disagreed:

  EACCES: permission denied, open '/x'   -> auth_failed
  Error: access denied writing to /x     -> permission_denied
  EROFS: read-only file system           -> permission_denied

Since UI copy is keyed off `code`, an operator whose backup destination
isn't writable is told their credentials were rejected, and goes off
rotating SSH keys when the fix is chown on the destination path.

Match the errno tokens ahead of the auth rule. EACCES/EROFS never appear
in SSH's "Permission denied (publickey)", so the two stay apart — the
existing auth_failed expectations are unchanged, and a test pins them.
@Hydralerne
Hydralerne merged commit a219193 into oblien:main Jul 22, 2026
2 checks passed
@shuvamk
shuvamk deleted the fix/core-eacces-permission-denied branch July 23, 2026 07:45
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.

2 participants