Skip to content

Instance.containsPath() compares mixed canonical/non-canonical paths #16660

@brndnblck

Description

@brndnblck

Description

After #16651, Instance.directory is always the canonical (symlink-resolved) path. But the filepath argument to containsPath() comes from various tool callers (bash.ts, external-directory.ts, file/index.ts) and is not canonicalized. Similarly, Instance.worktree may not be canonical.

This means a symlinked path passed to containsPath() won't match the canonical Instance.directory, causing:

  • bash.ts:89,129 — false negatives on the boundary check, triggering unnecessary external_directory permission prompts for paths that are actually inside the project
  • external-directory.ts:17 — same false negative, prompting for permission when none is needed
  • file/index.ts:503,585File.read() and File.tree() incorrectly reject symlinked paths within the project

The existing TODO comments at file/index.ts:501 and :583 acknowledge this as a known limitation ("lexical containment check"). #16651 makes it slightly more pronounced because Instance.directory is now always canonical while inputs remain uncanonicalized.

A fix would be to canonicalize the filepath argument inside containsPath():

containsPath(filepath: string) {
  const resolved = Filesystem.resolve(filepath)
  if (Filesystem.contains(Instance.directory, resolved)) return true
  if (Instance.worktree === "/") return false
  return Filesystem.contains(Instance.worktree, resolved)
}

Steps to reproduce

  1. Create a symlink to a project directory: ln -s ~/myproject ~/myproject-link
  2. Run opencode from the symlinked path: cd ~/myproject-link && opencode
  3. Use a tool that accesses a file via its symlinked path (e.g., bash tool with cwd set to the symlink path, or file read via symlink)
  4. Observe unnecessary external_directory permission prompts for paths that are actually inside the project

Operating System

macOS, Linux (anywhere symlinks are common)

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