anomalyco / anomalyco/opencode

Path args with Unicode whitespace (U+00A0 / U+202F) silently fail to resolve — propose NFKC-tolerant matching at the shared resolver

Open
#41,833 0 comments 0 reactions 1 assignee View on GitHub

@jlongster is already working on this.

Since Aug 11, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

File paths containing non-ASCII Unicode whitespace (e.g. U+00A0 NO-BREAK SPACE, U+202F NARROW NO-BREAK SPACE) silently fail to resolve when passed to a tool. By the time the path reaches a tool, these codepoints have been normalized to plain U+0020, so the string no longer byte-matches the file. Any tool that does an exact fs.existsSync then reports "file not found", while a glob against the same directory matches the real file.

This affects every tool that receives a local path — built-in read/edit/glob/grep and any MCP tool that reads files (e.g. a vision tool's image_source). The shared root cause is that the path bytes are lost in the rendering/transport chain before the resolver sees them.

Reproducible example

macOS with AppleLocale = ru_US. Take a UI screenshot → the system writes a filename such as:

Снимок экрана — 2026-08-11 в 10.07.55 PM.png

xxd of the real on-disk name contains:

... U+00A0 (NBSP) before the em-dash
... U+00A0 (NBSP) between "в" and the time
... U+202F (NARROW NBSP) before "PM"

i.e. the whitespace is not plain U+0020. When that path is then referenced (pasted into the TUI, carried in an error message, or emitted by the model as a tool argument), it arrives with U+0020 in all those positions. The tool then fails:

Image file not found: .../Снимок экрана — 2026-08-11 в 10.07.55 PM.png

while cp <dir>/*.png … (a glob) succeeds against the byte-true name.

Root cause

Lossy normalization of Unicode whitespace when a path is rendered into text (terminal / injected error message / model reproduction). Once normalized, no downstream exact-match layer can recover the original bytes — only the filesystem still has them. Cyrillic (and other non-whitespace Unicode) letters resolve fine; it is specifically the whitespace variants that break.

Verified: a path built with U+00A0fs.existsSync(realName) = true; the same path with U+0020 substituted = false. One codepoint of difference is enough.

Proposed fix

At the shared tool-argument path-resolution layer (the same place that logs "resolved path"), when an exact match fails, fall back to Unicode-tolerant matching against the parent directory:

// normalize both the input segment and each readdir entry, then compare
const norm = (s) => s.normalize("NFKC").replace(/\s+/g, " ").trim();

NFKC maps U+00A0/U+202F/U+2007/etc. to U+0020; folding whitespace runs handles the rest. Walk each path segment; on a literal miss, readdir the parent and take a unique NFKC-equivalent match. This recovers the byte-true path once, for all tools, instead of patching each consumer.

This is a class-level fix: it covers built-in tools and every MCP that reads local files, and it removes the need for per-MCP workarounds.

Scope / related

  • #23508 — provide the full/correct image path in the error message (same family: the path string the user/agent gets is unreliable).
  • #29216 — non-vision models blocked from passing images to vision-capable MCP tools.
  • #33695 — image read fails with a vision-capable local model.

Workaround in use today

I'm running a local tool.execute.before plugin that NFKC-resolves path-like args for all tools (built-in + MCP), plus a per-MCP patch on the path-validation function. Both work, but the right home for this is the shared resolver. Happy to contribute a PR if a maintainer points me at the resolver's location in packages/core.

Environment

  • opencode 1.18.16 (Mach-O arm64 build)
  • macOS, AppleLocale = ru_US

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.