[BUG] OSC 8 file-path hyperlinks use a scheme-less URI → macOS "The application can't be opened. -50"
Summary
Claude Code emits OSC 8 hyperlinks for file paths whose URI has no scheme (a bare /Users/... path rather than file:///Users/...). On macOS, handing LaunchServices a scheme-less URL fails with paramErr (-50), so CMD+Click in iTerm2 produces:
The application can't be opened. -50
This is distinct from #78109. That issue concerns pathToFileURL(path + ':' + line) percent-escaping the colon, which produces a well-formed file:// URI pointing at a nonexistent file (LaunchServices NSCocoaErrorDomain 260, "no such file"). This one has no scheme at all and fails earlier with a different error.
Environment
- Claude Code 2.1.212 (native installer build)
- macOS 26 (Darwin 25.5.0)
- iTerm2 3.6.11
- Reproduces with and without tmux, and with and without a
:linesuffix
Evidence
1. The rendered link has no scheme. Holding CMD and hovering a file path in Claude Code's output, iTerm2 shows the link target as /Users/me/.claude/skills/monitor-setup/SKILL.md — no file:// prefix. A hand-made OSC 8 link to the same file shows file:///Users/... on hover and opens correctly:
# works — opens the file
printf 'A \033]8;;file:///Users/me/.claude/skills/monitor-setup/SKILL.md\033\\CLICK\033]8;;\033\\\n'
2. -50 is an app-launch failure, not a file failure. Probing NSWorkspace.open(_:configuration:completionHandler:) directly on this machine:
| input | result |
|---|---|
| file:///…/SKILL.md | opens correctly |
| file:///…/SKILL.md:42 | NSCocoaErrorDomain 260 (no such file) |
| /…/SKILL.md (scheme-less) | NSCocoaErrorDomain 256 |
None of these yield -50 — because NSWorkspace.open parses the URL itself. The -50 arises in the LaunchServices path that iTerm2 uses, consistent with the Ghostty capture below.
3. Two different OSC 8 emitters exist in the bundle. Minified, from the 2.1.212 binary (names are from the compiled bundle and won't map to source):
// (a) always produces a scheme — this is #78109's site:
function e7(e){ if(!y0()) return e;
return `\x1B]8;;${UBd.pathToFileURL(e).href}\x07${e}\x1B]8;;\x07` }
// (b) interpolates the raw string as the URI — no scheme conversion:
var O2d="\x1B]8;;", P2d="\x07";
let u = (((r?.themeName)?wQn(r.themeName):!1)?gt.blue:gt.blueBright)(t??e);
return `${O2d}${e}${P2d}${u}${O2d}${P2d}`;
Site (b) uses e verbatim as the hyperlink target. When e is a filesystem path rather than a URL, the emitted OSC 8 URI is scheme-less. This matches the observed hover text exactly.
Prior art: Ghostty hit this and fixed it terminal-side
ghostty-org/ghostty#8599 / #9037 — "Claude Code Link to local files fails to open file: The application can't be opened -50" — fixed in Ghostty 1.2.1 via PR #8764. Their captured log confirms the mechanism:
LAUNCH: Launch failed in CSUI with error Error Domain=NSOSStatusErrorDomain
Code=-50 "paramErr: error in user parameter list"
…via _LSOpenStuffCallLocal in CoreServicesUIAgent. Their fix:
- guard let url = URL(string: action.url) else { ... }
+ // The URL initializer will gladly take invalid URLs (e.g. plain file paths) and turn
+ // them into schema-less URLs, but these won't open properly in text editors.
+ if let candidate = URL(string: action.url), candidate.scheme != nil { url = candidate }
+ else { url = URL(filePath: action.url) }
Ghostty defends against it; iTerm2 does not. The emitted URI is malformed either way.
Suggested fix
Ensure every OSC 8 URI carries a scheme. At minimum, site (b) should go through pathToFileURL() (or reject non-URL input) rather than interpolating a raw path.
Worth pairing with #78109's fix — emitting #line as a fragment rather than :line would additionally let iTerm2 route these through Semantic History, which resolves line numbers correctly (per iTerm2's docs, file: URLs with a # fragment are handled by Semantic History; file:///tmp/file.txt#123). A plain file:// URI cannot express a line number at all.
Workaround
FORCE_HYPERLINK=0 disables both emitters (y0() gate). It's the only lever — there's no settings.json key; FE() in the gate resolves to attacherCaps, not user config.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗