Skip to content

Bare catch in Filesystem.resolve() swallows non-ENOENT errors #16659

@brndnblck

Description

@brndnblck

Description

The try/catch in Filesystem.resolve() (introduced in #16651) catches all exceptions from realpathSync, not just ENOENT. Errors like EACCES (permission denied), ELOOP (too many symlink levels), and ENOTDIR (component is not a directory) are silently swallowed, and the function falls back to the unresolved path.

This means:

  • An EACCES on a symlink target silently resolves to the symlink path instead of surfacing the permission error
  • An ELOOP (symlink cycle) silently resolves to the unresolvable symlink path instead of alerting the user
  • Both could lead to Instance.provide() creating a context keyed on a non-canonical path, re-introducing the duplicate-instance class of bugs for those edge cases

The codebase already has an isEnoent() type guard at filesystem.ts:50 used elsewhere. The fix is to narrow the catch:

export function resolve(p: string): string {
  const resolved = pathResolve(windowsPath(p))
  try {
    return normalizePath(realpathSync(resolved))
  } catch (e) {
    if (isEnoent(e)) return normalizePath(resolved)
    throw e
  }
}

Steps to reproduce

  1. Create a symlink cycle: ln -s a b && ln -s b a
  2. Or create a symlink to a permission-denied directory
  3. Run opencode from a path involving that symlink
  4. Observe that the error is silently swallowed instead of surfaced

Operating System

All platforms

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions