{"record":{"id":"8dd88b691fed1a2a","repo":"can1357/oh-my-pi","slug":"seafile-share-response-did-not-include-a-location","errorCode":null,"errorMessage":"seafile share response did not include a Location URL","messagePattern":"seafile share response did not include a Location URL","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":433,"sourceCode":"\n\t\t\tconst shareForm = new URLSearchParams({\n\t\t\t\tp: `${directory === \"/\" ? \"\" : directory}/${filename}`,\n\t\t\t\tshare_type: \"download\",\n\t\t\t});\n\t\t\tif (sharePassword) shareForm.set(\"password\", sharePassword);\n\t\t\tif (expiryDays !== undefined && expiryDays > 0) shareForm.set(\"expire\", String(expiryDays));\n\t\t\tconst shareResponse = await expectOk(\n\t\t\t\tawait requestFetch(`${endpoint(apiUrl, \"repos\", repositoryId, \"file\", \"shared-link\")}/`, {\n\t\t\t\t\tmethod: \"PUT\",\n\t\t\t\t\theaders: { ...headers, \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\t\t\tbody: shareForm,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t}),\n\t\t\t\t\"seafile\",\n\t\t\t);\n\t\t\tlet location = shareResponse.headers.get(\"Location\") ?? undefined;\n\t\t\tif (!location) location = shareLink(parseJsonText(await shareResponse.text()));\n\t\t\tif (!location) throw new Error(\"seafile share response did not include a Location URL\");\n\t\t\tconst resultUrl = new URL(location, apiUrl);\n\t\t\tif (raw) resultUrl.searchParams.set(\"raw\", \"1\");\n\t\t\treturn publication(\"seafile\", request, resultUrl.toString(), {\n\t\t\t\t...(expiryInfo.expiresAt === undefined ? {} : { expiresAt: expiryInfo.expiresAt }),\n\t\t\t});\n\t\t},\n\t};\n}\n\nfunction plikFile(value: unknown): PlikFile | undefined {\n\tlet candidate: unknown = value;\n\tif (Array.isArray(candidate)) candidate = candidate[0];\n\tif (typeof candidate === \"object\" && candidate !== null && \"file\" in candidate) candidate = candidate.file;\n\tif (typeof candidate !== \"object\" || candidate === null || !(\"id\" in candidate)) return undefined;\n\tconst id = identifier(candidate.id);\n\tif (!id) return undefined;\n\tconst name =\n\t\t\"fileName\" in candidate","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L415-L451","documentation":"The Seafile uploader expects the share-link creation response to yield a public URL, either from the Location header of a redirect (fetch is called with redirect: \"manual\") or from the JSON body's shareLink field. If neither is present, the response cannot be turned into a share URL and the upload fails fast with this error. It guards against silent misconfiguration of the Seafile server or an unexpected API response shape.","triggerScenarios":"The POST to the Seafile share-link endpoint (api/v2.1/share-links/) returns 2xx but includes no Location header and its JSON body lacks a usable shareLink value — e.g. the server responds with an empty body, an error object with no link field, or a non-redirect response because the endpoint path or auth token is wrong.","commonSituations":"Misconfigured SEAFILE server URL or API token causing the server to answer with a JSON error instead of a share link; a Seafile version whose share-link response format differs (older API versions return different field names); a reverse proxy stripping Location headers on manual-redirect responses.","solutions":["Verify the Seafile API base URL and token: curl the share-link endpoint manually and inspect the response body/headers","Check the Seafile server version and confirm it returns shareLink (or sets Location) on POST /api/v2.1/share-links/","Check for a reverse proxy rewriting/stripping the Location header","Inspect the raw response body in logs to see the server's error message (e.g. permission denied on the library)"],"exampleFix":"// before: server returns error JSON with no shareLink\nif (!location) throw new Error(\"seafile share response did not include a Location URL\");\n// after: log the body to diagnose\nif (!location) throw new Error(`seafile share response did not include a Location URL: ${await shareResponse.text()}`);","handlingStrategy":"try-catch","validationCode":"const res = await fetch(shareUrl, { method: \"POST\", headers, redirect: \"manual\" });\nconst loc = res.headers.get(\"Location\") ?? (await res.json())?.shareLink;\nif (!loc) throw new Error(`Seafile returned no share link: HTTP ${res.status}`);","typeGuard":"function hasShareLink(v: unknown): v is { shareLink: string } {\n\treturn typeof v === \"object\" && v !== null && typeof (v as { shareLink?: unknown }).shareLink === \"string\";\n}","tryCatchPattern":"try {\n\tawait uploader.upload(req);\n} catch (err) {\n\tif (err instanceof Error && err.message.includes(\"seafile share response\")) {\n\t\t// log Seafile base URL/token, inspect server response, surface actionable message\n\t}\n\tthrow err;\n}","preventionTips":["Verify Seafile API token and base URL with curl before wiring into config","Test POST /api/v2.1/share-links/ manually and confirm shareLink/Location is present","Watch for reverse proxies stripping Location headers","Pin/verify Seafile server version compatibility"],"tags":["seafile","http-response","missing-header","self-hosted"],"backgroundTag":"missing-location-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}