musistudio/claude-code-router · error · Error

Unsupported link protocol.

Error message

Unsupported link protocol.

What it means

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.

Source

Thrown at packages/core/src/contracts/deep-link.ts:78

  } catch (error) {
    return {
      error: error instanceof Error ? error.message : String(error),
      id,
      rawUrl,
      receivedAt: receivedAt.toISOString()
    };
  }
}

export function parseProviderManifestDeepLinkPayload(rawUrl: string): ProviderManifestDeepLinkPayload | undefined {
  const value = rawUrl.trim();
  if (value.length > maxDeepLinkLength) {
    throw new Error("Provider link is too long.");
  }

  const url = new URL(value);
  if (url.protocol !== `${appDeepLinkProtocol}:`) {
    throw new Error("Unsupported link protocol.");
  }

  const host = url.hostname.toLowerCase();
  const firstPathSegment = url.pathname.split("/").filter(Boolean)[0]?.toLowerCase();
  if (host !== providerDeepLinkHost && firstPathSegment !== providerDeepLinkHost) {
    throw new Error("Unsupported CCR link target.");
  }

  const payload = readPayloadRecord(url.searchParams);
  const manifestUrl = boundedString(
    firstStringParam(url.searchParams, ["manifest"]) ??
      firstPayloadString(payload, ["manifest"]),
    maxManifestUrlLength,
    "Manifest URL"
  );
  if (!manifestUrl) {
    return undefined;
  }

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Ensure the link starts with ccr:// (the current appDeepLinkProtocol)
  2. If generating links, build them with the same constant the parser uses rather than hardcoding
  3. Check for transport mangling (encoded %3A instead of ':') and decode before parsing

Example fix

// before
parseProviderManifestDeepLinkPayload("https://provider/install?manifest=...");
// after
parseProviderManifestDeepLinkPayload("ccr://provider/install?manifest=...");
Defensive patterns

Strategy: try-catch

Validate before calling

if (!rawUrl.trim().toLowerCase().startsWith(`${appDeepLinkProtocol}://`)) throw new TypeError("not a ccr link");

Type guard

const isCcrLink = (u: string) => new URL(u.trim()).protocol === "ccr:";

Try / catch

try { parseProviderManifestDeepLinkPayload(url); } catch (e) { if (e instanceof Error && e.message === "Unsupported link protocol.") return ignore(url); throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/734aa60dd885299a. Report an issue: GitHub.