{"record":{"id":"891cb10bbfbbf394","repo":"can1357/oh-my-pi","slug":"share-upload-to-base-failed-server-returned-no","errorCode":null,"errorMessage":"Share upload to ${base} failed: server returned no usable id","messagePattern":"Share upload to (.+?) failed: server returned no usable id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/export/share.ts","lineNumber":684,"sourceCode":"async function uploadToServer(sealed: Uint8Array, base: string): Promise<string> {\n\tlet res: Response;\n\ttry {\n\t\tres = await fetch(base, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/octet-stream\" },\n\t\t\tbody: sealed,\n\t\t});\n\t} catch (err) {\n\t\tthrow new Error(`Share upload to ${base} failed: ${err instanceof Error ? err.message : String(err)}`);\n\t}\n\tif (!res.ok) {\n\t\tconst detail = (await res.text().catch(() => \"\")).trim().slice(0, 200);\n\t\tthrow new Error(`Share upload to ${base} failed: HTTP ${res.status}${detail ? ` (${detail})` : \"\"}`);\n\t}\n\tconst body = (await res.json().catch(() => null)) as { id?: unknown } | null;\n\tconst id = body && typeof body.id === \"string\" ? body.id : \"\";\n\tif (!/^[A-Za-z0-9_-]{10,64}$/.test(id)) {\n\t\tthrow new Error(`Share upload to ${base} failed: server returned no usable id`);\n\t}\n\treturn id;\n}\n","sourceCodeStart":666,"sourceCodeEnd":688,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/export/share.ts#L666-L688","documentation":"After a successful POST, uploadToServer parses the JSON response and validates that body.id is a string matching /^[A-Za-z0-9_-]{10,64}$/. If the server's JSON lacks a usable id (different shape, empty id, HTML error page parsed as null), the upload is considered failed because the returned id is what composes the share URL.","triggerScenarios":"The share server responds 2xx but with a body that is not the expected {id} JSON: an API change on the server, a proxy returning an HTML page with 200, a server returning {url: ...} instead of {id: ...}, or an id that is too short/long or contains invalid characters.","commonSituations":"Self-hosted share server running an incompatible version (older/newer response schema); CDN or captive portal injecting HTML into a 200 response; misconfigured server returning an empty body with 200.","solutions":["Log/inspect the raw server response to see what was actually returned; align the server to return {\"id\":\"<10-64 chars of [A-Za-z0-9_-]>\"}.","Update the self-hosted share server to the matching version of the client.","Check for a proxy/CDN or captive portal rewriting responses; bypass it and test directly.","If you control both sides and want full URLs, extend the client validation instead of shipping non-conforming ids."],"exampleFix":"// before (server)\nres.json({ url: `https://share.example.com/s/${token}` });\n// after (server)\nres.json({ id: token }); // token matches /^[A-Za-z0-9_-]{10,64}$/","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasUsableId(body: unknown): body is { id: string } {\n  return typeof body === 'object' && body !== null\n    && typeof (body as { id?: unknown }).id === 'string'\n    && /^[A-Za-z0-9_-]{10,64}$/.test((body as { id: string }).id);\n}","tryCatchPattern":"try {\n  const id = await uploadToServer(base, sealed);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('no usable id')) {\n    // capture raw response for diagnostics; check server/client version compatibility\n  } else throw err;\n}","preventionTips":["Keep the share server and client versions in sync","Ensure the server always returns {\"id\": \"...\"} JSON with Content-Type application/json on success","Avoid proxies/CDNs that can rewrite or replace 200 response bodies","Write an integration test asserting the response contract"],"tags":["http","api-contract","validation","sharing"],"backgroundTag":"unexpected-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}