{"record":{"id":"734aa60dd885299a","repo":"musistudio/claude-code-router","slug":"unsupported-link-protocol","errorCode":null,"errorMessage":"Unsupported link protocol.","messagePattern":"Unsupported link protocol\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/contracts/deep-link.ts","lineNumber":78,"sourceCode":"  } catch (error) {\n    return {\n      error: error instanceof Error ? error.message : String(error),\n      id,\n      rawUrl,\n      receivedAt: receivedAt.toISOString()\n    };\n  }\n}\n\nexport function parseProviderManifestDeepLinkPayload(rawUrl: string): ProviderManifestDeepLinkPayload | undefined {\n  const value = rawUrl.trim();\n  if (value.length > maxDeepLinkLength) {\n    throw new Error(\"Provider link is too long.\");\n  }\n\n  const url = new URL(value);\n  if (url.protocol !== `${appDeepLinkProtocol}:`) {\n    throw new Error(\"Unsupported link protocol.\");\n  }\n\n  const host = url.hostname.toLowerCase();\n  const firstPathSegment = url.pathname.split(\"/\").filter(Boolean)[0]?.toLowerCase();\n  if (host !== providerDeepLinkHost && firstPathSegment !== providerDeepLinkHost) {\n    throw new Error(\"Unsupported CCR link target.\");\n  }\n\n  const payload = readPayloadRecord(url.searchParams);\n  const manifestUrl = boundedString(\n    firstStringParam(url.searchParams, [\"manifest\"]) ??\n      firstPayloadString(payload, [\"manifest\"]),\n    maxManifestUrlLength,\n    \"Manifest URL\"\n  );\n  if (!manifestUrl) {\n    return undefined;\n  }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/contracts/deep-link.ts#L60-L96","documentation":"Thrown when the parsed deep link's protocol is not the app deep-link protocol (appDeepLinkProtocol, i.e. ccr:). parseProviderManifestDeepLinkPayload only accepts its own scheme; anything else (https:, ccr-dev:, typo'd scheme) is rejected before host validation.","triggerScenarios":"Calling parseProviderManifestDeepLinkPayload with a URL whose url.protocol !== 'ccr:' — e.g. an https:// web link, a differently-prefixed custom scheme, or a leading whitespace/scheme typo surviving trim.","commonSituations":"Passing a regular web URL by mistake; OS rewrote or prefixed the custom scheme; scheme renamed between app versions; copy-paste dropped the 'ccr://' prefix so URL() inferred another protocol.","solutions":["Ensure the link starts with ccr:// (the current appDeepLinkProtocol)","If generating links, build them with the same constant the parser uses rather than hardcoding","Check for transport mangling (encoded %3A instead of ':') and decode before parsing"],"exampleFix":"// before\nparseProviderManifestDeepLinkPayload(\"https://provider/install?manifest=...\");\n// after\nparseProviderManifestDeepLinkPayload(\"ccr://provider/install?manifest=...\");","handlingStrategy":"try-catch","validationCode":"if (!rawUrl.trim().toLowerCase().startsWith(`${appDeepLinkProtocol}://`)) throw new TypeError(\"not a ccr link\");","typeGuard":"const isCcrLink = (u: string) => new URL(u.trim()).protocol === \"ccr:\";","tryCatchPattern":"try { parseProviderManifestDeepLinkPayload(url); } catch (e) { if (e instanceof Error && e.message === \"Unsupported link protocol.\") return ignore(url); throw e; }","preventionTips":["Build links from the exported appDeepLinkProtocol constant","Route OS deep-link events through a scheme check first"],"tags":["deep-link","protocol","validation"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}