{"record":{"id":"206cdc3d7715693b","repo":"remix-run/remix","slug":"invalid-protocol","errorCode":null,"errorMessage":"invalid protocol","messagePattern":"invalid protocol","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/route-pattern/src/lib/route-pattern/parse.ts","lineNumber":258,"sourceCode":"    if (token.type === ':' || token.type === '*') {\n      structure += token.type\n      captures += `${token.type}:${token.name}|`\n    } else if (token.type === 'text') {\n      structure += `t:${token.text}|`\n    } else {\n      structure += `${token.type}|`\n    }\n  }\n  return { structure, captures }\n}\n\nfunction parseProtocol(source: string, span: Span | null): RoutePatternParts['protocol'] {\n  if (!span) return null\n  let protocol = source.slice(...span)\n  if (protocol === '' || protocol === 'http' || protocol === 'https' || protocol === 'http(s)') {\n    return protocol === '' ? null : protocol\n  }\n  throw new ParseError('invalid protocol', source, span[0])\n}\n\nfunction parseHostname(source: string, span: Span | null): RoutePatternParts['hostname'] | null {\n  if (!span) return null\n  let part = parsePart(source, { span, type: 'hostname' })\n  if (isNamelessWildcard(part)) return null\n  return part\n}\n\nfunction isNamelessWildcard(part: PartPattern): boolean {\n  if (part.tokens.length !== 1) return false\n  let token = part.tokens[0]\n  if (token.type !== '*') return false\n  return token.name === '*'\n}\n\nfunction parseSearch(source: string): RoutePatternParts['search'] {\n  let constraints = new Map<string, Set<string>>()","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/route-pattern/src/lib/route-pattern/parse.ts#L240-L276","documentation":"Thrown by parseProtocol when parsing a route pattern whose protocol segment is not one of the supported values. Remix route patterns only allow 'http', 'https', or 'http(s)' before the '://' separator; anything else is rejected. The error is a ParseError that includes the source position of the offending protocol.","triggerScenarios":"Calling parsePattern (directly or via route configuration) with a pattern like 'ftp://host/path' or 'ws://host/path', or any protocol segment other than '', 'http', 'https', 'http(s)'.","commonSituations":"Copying a URL with an unsupported scheme (ftp:, ws:, wss:) into a route pattern; typos like 'httpx://' or trailing whitespace in the protocol segment; assuming any URL scheme is a valid pattern prefix.","solutions":["Change the protocol segment to 'http', 'https', or 'http(s)'","Remove the protocol segment entirely if you don't need protocol matching","Check for typos or stray characters before the '://' in the pattern"],"exampleFix":"// before\nparsePattern('ftp://example.com/files/*')\n// after\nparsePattern('http(s)://example.com/files/*')","handlingStrategy":"validation","validationCode":"let PROTOCOLS = new Set(['http', 'https', 'http(s)'])\nfunction hasValidProtocol(pattern: string): boolean {\n  let m = /^([a-z]+\\(s\\)|[a-z]+):\\/\\//.exec(pattern)\n  return m == null || PROTOCOLS.has(m[1])\n}","typeGuard":"function isSupportedProtocol(protocol: string): protocol is 'http' | 'https' | 'http(s)' {\n  return protocol === 'http' || protocol === 'https' || protocol === 'http(s)'\n}","tryCatchPattern":"try { parsePattern(pattern) } catch (e) { if (e instanceof ParseError) return fallbackRoute(e) ; throw e }","preventionTips":["Validate pattern strings at app startup","Keep supported protocols documented next to route config"],"tags":["route-pattern","parsing","protocol"],"backgroundTag":"pattern-parse-error","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}