{"record":{"id":"64d7dbcf2b620520","repo":"Hmbown/CodeWhale","slug":"mobile-stream-ticket-was-unavailable","errorCode":null,"errorMessage":"Mobile stream ticket was unavailable","messagePattern":"Mobile stream ticket was unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_mobile.html","lineNumber":403,"sourceCode":"      }\n      if (!res.ok) {\n        let detail = await res.text();\n        try {\n          const parsed = JSON.parse(detail);\n          detail = parsed.error?.message || detail;\n        } catch (_) {}\n        throw new Error(detail || (\"HTTP \" + res.status));\n      }\n      useMobileSession(await res.json());\n    }\n\n    async function refreshStreamTicket() {\n      const result = await api(\"/__codewhale/mobile/stream-ticket\", {\n        method: \"POST\",\n        body: \"{}\"\n      });\n      if (!result || typeof result.stream_ticket !== \"string\") {\n        throw new Error(\"Mobile stream ticket was unavailable\");\n      }\n      state.streamTicket = result.stream_ticket;\n      saveMobileSession();\n      return state.streamTicket;\n    }\n\n    async function takeStreamTicket() {\n      if (!state.requestProof) return \"\";\n      const ticket = state.streamTicket || await refreshStreamTicket();\n      state.streamTicket = \"\";\n      saveMobileSession();\n      return ticket;\n    }\n\n    function escapeHtml(raw) {\n      return String(raw).replace(/[&<>\"']/g, (char) => ({\n        \"&\": \"&amp;\",\n        \"<\": \"&lt;\",","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_mobile.html#L385-L421","documentation":"refreshStreamTicket fetches a new stream ticket via POST /__codewhale/mobile/stream-ticket and validates the result. If the response is empty or `stream_ticket` is not a string, the runtime cannot resume/authenticate the event stream, so it throws.","triggerScenarios":"The stream-ticket endpoint returns 204/null, a body without `stream_ticket`, or a non-string ticket value (server schema change or backend failure).","commonSituations":"Runtime backend failing to mint tickets; version mismatch between mobile page and server response shape; transient server error already surfaced through api() and a malformed fallback body.","solutions":["Retry refreshStreamTicket(); transient backend errors often resolve.","Check the Runtime version serving the page matches the endpoint's response schema.","Inspect the endpoint's raw response (curl with the same auth) to see what is actually returned."],"exampleFix":"// before\nconst ticket = await refreshStreamTicket();\n// after\nlet ticket;\ntry { ticket = await refreshStreamTicket(); }\ncatch (e) { ticket = await refreshStreamTicket(); /* retry once */ }","handlingStrategy":"retry","validationCode":"if (result && typeof result.stream_ticket !== 'string') {\n  throw new Error('bad ticket payload');\n}","typeGuard":"function hasStreamTicket(r) {\n  return !!r && typeof r.stream_ticket === 'string' && r.stream_ticket.length > 0;\n}","tryCatchPattern":"try {\n  ticket = await refreshStreamTicket();\n} catch {\n  await new Promise((r) => setTimeout(r, 500));\n  ticket = await refreshStreamTicket();\n}","preventionTips":["Retry ticket refresh with backoff on failure.","Verify server/page version alignment after upgrades.","Fall back to a full session reconnect if tickets repeatedly fail."],"tags":["streaming","mobile","api"],"backgroundTag":"unexpected-response-shape","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}