{"record":{"id":"663395bb450c280c","repo":"can1357/oh-my-pi","slug":"path-filepath-uses-internal-scheme-local","errorCode":null,"errorMessage":"Path \"${filePath}\" uses internal scheme \"local://\" and must be resolved through the proper protocol handler, not as a filesystem path.","messagePattern":"Path \"(.+?)\" uses internal scheme \"local://\" and must be resolved through the proper protocol handler, not as a filesystem path\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/utils.ts","lineNumber":17,"sourceCode":"import * as path from \"node:path\";\nimport { postmortem } from \"@oh-my-pi/pi-utils\";\nimport { theme } from \"../modes/theme/theme\";\nimport { expandPath, normalizeLocalScheme } from \"../tools/path-utils\";\nimport type { HookUIContext } from \"./hooks/types\";\n\n/**\n * Resolve a file path:\n * - Absolute paths used as-is\n * - Paths starting with ~ expanded to home directory\n * - Relative paths resolved from cwd\n */\nexport function resolvePath(filePath: string, cwd: string): string {\n\tconst expanded = expandPath(filePath);\n\tconst expandedAndNormalized = normalizeLocalScheme(expanded);\n\tif (expandedAndNormalized.startsWith(\"local://\")) {\n\t\tthrow new Error(\n\t\t\t`Path \"${filePath}\" uses internal scheme \"local://\" and must be resolved through the proper protocol handler, not as a filesystem path.`,\n\t\t);\n\t}\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\treturn path.resolve(cwd, expanded);\n}\n\n/**\n * Create a no-op UI context for headless modes.\n */\nexport function createNoOpUIContext(): HookUIContext {\n\treturn {\n\t\tselect: async () => undefined,\n\t\tconfirm: async () => false,\n\t\tinput: async () => undefined,\n\t\tnotify: () => {},","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/utils.ts#L1-L35","documentation":"resolvePath turns user-facing path strings into filesystem paths (~ expansion, cwd-relative resolution) but `local://` is an internal opencode scheme handled by dedicated protocol handlers, not a real filesystem path. If, after expandPath and normalizeLocalScheme, the string still starts with `local://`, the caller is using the wrong resolver — resolving it as a file would silently produce a bogus path like `cwd/local://...`. The throw is a fail-fast guard against that.","triggerScenarios":"Calling resolvePath with a path string of the form `local://...` — e.g. a plugin/hook config or tool argument that stores a `local://` URI, code passing a previously stored internal resource reference back into resolvePath, or a `local://` prefix that normalizeLocalScheme did not consume (e.g. doubled or malformed scheme).","commonSituations":"A user pastes `local:///home/me/project/file.ts` into a plugin config where a plain path is expected; an extension round-trips an internal URI through a file-path API; a hook records `local://` references in config after an editor interaction and the plugin later tries to resolve them as disk paths.","solutions":["Strip the scheme and resolve the underlying path, or route the value through the `local://` protocol handler instead of resolvePath.","Convert stored `local://` references to plain absolute/relative filesystem paths in your config before they reach resolvePath.","If you only need the path portion, remove the prefix (e.g. `p.replace(/^local:\\/\\//, \"\")`) and pass the result — but prefer the proper protocol API.","Check upstream producers of the string: they should emit plain paths when the consumer is a filesystem resolver."],"exampleFix":"// before\nconst abs = resolvePath(config.filePath, cwd); // config.filePath = \"local:///repo/src/a.ts\"\n// after\nconst abs = config.filePath.startsWith(\"local://\")\n\t? resolvePath(config.filePath.slice(\"local://\".length), cwd)\n\t: resolvePath(config.filePath, cwd);","handlingStrategy":"validation","validationCode":"function isFilesystemPath(p: string): boolean {\n\treturn !p.startsWith(\"local://\");\n}\nif (!isFilesystemPath(filePath)) {\n\t// route through the protocol handler instead of resolvePath\n}","typeGuard":"function isLocalSchemeUri(v: string): boolean {\n\treturn v.startsWith(\"local://\");\n}\n// usage: if (isLocalSchemeUri(p)) handleViaProtocol(p); else resolvePath(p, cwd);","tryCatchPattern":"let abs: string;\ntry {\n\tabs = resolvePath(filePath, cwd);\n} catch (err) {\n\tif (err instanceof Error && err.message.includes('internal scheme \"local://\"')) {\n\t\tabs = resolveLocalSchemeThroughHandler(filePath); // proper protocol path\n\t} else throw err;\n}","preventionTips":["Store plain filesystem paths in configs consumed by resolvePath; reserve `local://` for internal resource references.","When passing values between subsystems, keep track of whether a value is a URI or a path and use the matching resolver.","Check inputs for a scheme prefix before path resolution and route accordingly.","Never construct `local://` strings by hand; obtain them only from APIs that issue them."],"tags":["path-resolution","uri-scheme","configuration","plugin"],"backgroundTag":"internal-uri-scheme-in-filesystem-path","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}