{"record":{"id":"0c7c1b243cac2d15","repo":"paperclipai/paperclip","slug":"non-numeric-port-in-web-key-json-stringify-key","errorCode":null,"errorMessage":"non-numeric port in Web key: ${JSON.stringify(key)}","messagePattern":"non-numeric port in Web key: (.+?)","errorType":"validation","errorClass":"ServeParseError","httpStatus":null,"severity":"error","filePath":"packages/tailscale-https-broker/src/serve-config.ts","lineNumber":48,"sourceCode":"  constructor(message: string) {\n    super(message);\n    this.name = \"ServeParseError\";\n  }\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** Extract the trailing `:port` of a `host:port` Web key. */\nfunction portFromWebKey(key: string): number {\n  const idx = key.lastIndexOf(\":\");\n  if (idx <= 0 || idx === key.length - 1) {\n    throw new ServeParseError(`malformed Web key: ${JSON.stringify(key)}`);\n  }\n  const portText = key.slice(idx + 1);\n  if (!/^[0-9]+$/.test(portText)) {\n    throw new ServeParseError(`non-numeric port in Web key: ${JSON.stringify(key)}`);\n  }\n  return assertCanonicalPort(Number(portText));\n}\n\n/**\n * Parse serve status JSON into a normalized, port-keyed model. Tolerates extra\n * top-level fields (Tailscale evolves its schema) but strictly validates the\n * `TCP` and `Web` shapes it depends on.\n */\nexport function parseServeStatus(json: unknown): ParsedServe {\n  if (!isObject(json)) {\n    throw new ServeParseError(\"serve status must be a JSON object\");\n  }\n  const entries = new Map<number, ServeEntry>();\n\n  const tcp = json.TCP;\n  const httpsPorts = new Set<number>();\n  if (tcp !== undefined && tcp !== null) {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/packages/tailscale-https-broker/src/serve-config.ts#L30-L66","documentation":"ServeParseError from portFromWebKey: the Web key's trailing segment after the last colon is not digits-only, so it cannot be interpreted as a port number. Tailscale's serve status JSON contained an unexpected Web key shape.","triggerScenarios":"Thrown at packages/tailscale-https-broker/src/serve-config.ts:48 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Correct the Web key so its port component is numeric."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","contentChangedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}