{"record":{"id":"f66356c0f3ed18b3","repo":"can1357/oh-my-pi","slug":"invalid-url-input","errorCode":null,"errorMessage":"Invalid URL: ${input}","messagePattern":"Invalid URL: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/parse.ts","lineNumber":63,"sourceCode":"}\n\n/**\n * Parse an internal URL into an InternalUrl.\n *\n * Handles URLs where `new URL()` would fail (e.g., `skill://plugin:name`\n * where the colon is not a port separator).\n */\nexport function parseInternalUrl(input: string): InternalUrl {\n\tconst hostMatch = input.match(SCHEME_HOST_RE);\n\tconst pathMatch = input.match(PATHNAME_RE);\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(input);\n\t} catch {\n\t\t// URL parse failed — build a minimal URL-like object from regex matches.\n\t\tif (!hostMatch) {\n\t\t\tthrow new Error(`Invalid URL: ${input}`);\n\t\t}\n\t\t// Extract search and hash from the raw input before constructing the object.\n\t\tconst hashIdx = input.indexOf(\"#\");\n\t\tconst hash = hashIdx !== -1 ? input.slice(hashIdx) : \"\";\n\t\tconst withoutHash = hashIdx !== -1 ? input.slice(0, hashIdx) : input;\n\t\tconst queryIdx = withoutHash.indexOf(\"?\");\n\t\tconst search = queryIdx !== -1 ? withoutHash.slice(queryIdx) : \"\";\n\t\tconst queryString = search.slice(1); // strip leading ?\n\n\t\t// Strip search/hash from pathname captured by regex.\n\t\tlet rawPathname = pathMatch?.[1] ?? \"\";\n\t\tif (queryIdx !== -1 && rawPathname.includes(\"?\")) {\n\t\t\trawPathname = rawPathname.slice(0, rawPathname.indexOf(\"?\"));\n\t\t}\n\n\t\tparsed = {\n\t\t\tprotocol: `${hostMatch[1]}:`,\n\t\t\thostname: hostMatch[2] ?? \"\",","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/parse.ts#L45-L81","documentation":"parseInternalUrl first attempts standard URL parsing; on failure it falls back to regex extraction. If neither the URL constructor nor the regex can find a host/scheme structure, the input is not a parseable internal URL and this error is thrown with the raw input echoed.","triggerScenarios":"Calling parseInternalUrl (directly or via router.resolve/write) with a string lacking a recognizable scheme://host structure — e.g. a bare filename, empty string, whitespace, or garbage text.","commonSituations":"Passing a plain file path where a URL is expected; trimming/whitespace mistakes; tool output accidentally fed back as a URL.","solutions":["Ensure the input includes a supported scheme prefix, e.g. 'omp://docs' or 'memory://summary.md'.","Trim whitespace and verify the value is a URL, not a filesystem path; use file:// for paths.","Validate with a quick regex like /^[a-z][a-z0-9+.-]*:\\/\\//i before calling."],"exampleFix":"// before\nparseInternalUrl(userText);\n// after\nif (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(userText.trim())) throw new Error('expected scheme:// URL');\nparseInternalUrl(userText.trim());","handlingStrategy":"validation","validationCode":"const URL_LIKE = /^[a-z][a-z0-9+.-]*:\\/\\/\\S+/i;\nif (!URL_LIKE.test(input.trim())) throw new Error(`not an internal URL: ${input}`);","typeGuard":"const looksLikeUrl = (s: string): boolean => /^[a-z][a-z0-9+.-]*:\\/\\//i.test(s.trim());","tryCatchPattern":"try {\n  return parseInternalUrl(input);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Invalid URL:')) throw new Error(`expected scheme:// URL, got: ${input}`);\n  throw err;\n}","preventionTips":["Validate input matches scheme://host shape before calling the parser.","Trim whitespace and reject empty strings early.","Distinguish file paths from URLs at the API boundary; wrap paths in file:// explicitly."],"tags":["url-parsing","input-validation","internal-url"],"backgroundTag":"invalid-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}