{"record":{"id":"eff867a65e4fb659","repo":"can1357/oh-my-pi","slug":"gist-fetch-failed-http-res-status","errorCode":null,"errorMessage":"Gist fetch failed: HTTP ${res.status}","messagePattern":"Gist fetch failed: HTTP (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/export/html/share-loader.js","lineNumber":36,"sourceCode":"      function decodeBase64(text) {\n        var binary = atob(text);\n        var bytes = new Uint8Array(binary.length);\n        for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n        return bytes;\n      }\n\n      function decodeBase64Url(text) {\n        var b64 = text.replace(/-/g, '+').replace(/_/g, '/');\n        while (b64.length % 4) b64 += '=';\n        return decodeBase64(b64);\n      }\n\n      async function fetchGistBlob(id) {\n        var res = await fetch('https://api.github.com/gists/' + id, {\n          headers: { Accept: 'application/vnd.github+json' },\n        });\n        if (res.status === 404) throw new Error('This share no longer exists (gist deleted?).');\n        if (!res.ok) throw new Error('Gist fetch failed: HTTP ' + res.status);\n        var gist = await res.json();\n        var files = Object.values(gist.files || {});\n        var file = files.find(function(f) { return /\\.ompshare\\.txt$/.test(f.filename); }) || files[0];\n        if (!file) throw new Error('Gist has no files.');\n        var text = file.content;\n        if (!text || file.truncated) {\n          var raw = await fetch(file.raw_url);\n          if (!raw.ok) throw new Error('Gist raw fetch failed: HTTP ' + raw.status);\n          text = await raw.text();\n        }\n        return decodeBase64(text.replace(/\\s+/g, ''));\n      }\n\n      async function fetchServerBlob(id) {\n        var res = await fetch('/s/' + id + '/raw');\n        if (res.status === 404 || res.status === 410) {\n          throw new Error('This share no longer exists (expired or deleted).');\n        }","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/export/html/share-loader.js#L18-L54","documentation":"Browser-side companion to the 404 case: fetchGistBlob throws 'Gist fetch failed: HTTP <status>' when GitHub's Gists API responds with any non-ok status other than 404 (e.g. 401, 403, 429, 5xx). The page cannot download the sealed payload, so the viewer sees this message. Most commonly 403 rate limiting, since the request is unauthenticated.","triggerScenarios":"Opening a shared-session page when the unauthenticated GET api.github.com/gists/<id> returns 403 (rate limit / regional block), 401, 429, or 5xx — anything except 404.","commonSituations":"Shared GitHub IP ranges rate-limited (403 with 'rate limit exceeded'); corporate proxy/firewall intercepting api.github.com; GitHub incident causing 5xx; viewer behind VPN flagged by GitHub.","solutions":["Retry later — unauthenticated GitHub API limits are per-IP and reset hourly.","Check the HTTP status shown: 403 usually means rate limiting; verify at api.github.com/rate_limit.","Try from a different network (disable VPN/proxy) to rule out IP blocks.","If persistent, the author can re-share using a custom share script that doesn't rely on anonymous Gist fetches.","Check the GitHub status page for ongoing incidents if seeing 5xx."],"exampleFix":"// before: nothing to fix in page code — network condition\n// after (author-side alternative): ~/.omp/agent/share.ts with a custom host\nexport default async (htmlPath: string) => {\n  const url = await uploadToMyBucket(htmlPath);\n  return { url };\n};","handlingStrategy":"retry","validationCode":"// check unauthenticated quota before loading a share\nconst rl = await fetch(\"https://api.github.com/rate_limit\").then(r => r.json());\nif (rl.resources.core.remaining === 0) {\n  console.warn(\"GitHub API quota exhausted; wait until\", new Date(rl.resources.core.reset * 1000));\n}","typeGuard":"null","tryCatchPattern":"async function loadShareWithRetry(id: string, tries = 3) {\n  for (let i = 0; i < tries; i++) {\n    try {\n      return await fetchGistBlob(id);\n    } catch (err) {\n      const m = err instanceof Error ? err.message : String(err);\n      if (!/HTTP (429|5\\d\\d)/.test(m) || i === tries - 1) throw err;\n      await new Promise(r => setTimeout(r, 2 ** i * 1000));\n    }\n  }\n}","preventionTips":["Expect unauthenticated GitHub API limits (~60/hr per IP) — retry after reset for 403/429.","Disable VPN/proxy when shares fail with non-404 HTTP errors to rule out IP blocks.","Authors wanting reliable shares should use a custom share script on their own hosting.","Watch the HTTP status in the message: 403/429 = rate limit, 5xx = GitHub-side, retry later."],"tags":["network","github","http","rate-limit","sharing"],"backgroundTag":"github-api-rate-limit","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}