{"record":{"id":"4e945e97b143700a","repo":"can1357/oh-my-pi","slug":"the-upload-node-did-not-return-a-direct-image-url","errorCode":null,"errorMessage":"the upload node did not return a direct image URL","messagePattern":"the upload node did not return a direct image URL","errorType":"exception","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":460,"sourceCode":"function createSendSpaceUploader(config: DestinationRuntimeConfig, endpoint: URL): BlobUploader {\n\tconst destination = \"sendspace\" as const;\n\treturn {\n\t\tdestination,\n\t\tasync upload(request: BlobUploadRequest) {\n\t\t\ttry {\n\t\t\t\tconst node = await discoverSendSpaceNode(config, endpoint);\n\t\t\t\tconst body = multipartFile(request, \"userfile\", {\n\t\t\t\t\tMAX_FILE_SIZE: node.maxFileSize,\n\t\t\t\t\tUPLOAD_IDENTIFIER: node.uploadIdentifier,\n\t\t\t\t\textra_info: node.extraInfo,\n\t\t\t\t});\n\t\t\t\tconst response = await fetchFor(config)(node.url, { method: \"POST\", body });\n\t\t\t\tawait expectOk(response, destination);\n\t\t\t\tconst text = await response.text();\n\t\t\t\tconst status = xmlElement(text, \"status\");\n\t\t\t\tconst raw = xmlElement(text, \"direct_url\") ?? xmlElement(text, \"download_url\");\n\t\t\t\tif (status !== \"ok\" || !raw) {\n\t\t\t\t\tthrow new LegacyDestinationError(destination, \"the upload node did not return a direct image URL\");\n\t\t\t\t}\n\t\t\t\tconst deleteUrl = xmlElement(text, \"delete_url\");\n\t\t\t\treturn publication(\n\t\t\t\t\tdestination,\n\t\t\t\t\trequest,\n\t\t\t\t\thttpUrl(destination, raw),\n\t\t\t\t\tdeleteUrl ? { delete: { method: \"GET\", url: httpUrl(destination, deleteUrl) } } : undefined,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tthrow failure(destination, error);\n\t\t\t}\n\t\t},\n\t};\n}\n\nfunction incompatible(destination: BlobDestinationId, reason: string): never {\n\tthrow new DestinationUnavailableError(destination, reason);\n}","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L442-L478","documentation":"Thrown after a POST to the legacy upload node when the response status is ok but the XML body lacks status=ok or a direct_url/download_url element, meaning the node accepted the request but did not report a usable image URL. The library cannot publish without a direct link, so it raises LegacyDestinationError.","triggerScenarios":"POSTing the multipart body to the discovery-provided node URL returns XML without <status>ok</status> or without a <direct_url>/<download_url> element — e.g. the node rejected the file silently, returned a rate-limit or auth-error XML, or uses different element names.","commonSituations":"Image host changed its response schema (renamed direct_url); upload rejected due to missing API key/token in the multipart form; file exceeds the node's size limit but the host signals it via a non-ok status; interim HTML error page replaces the XML response.","solutions":["Inspect the raw response body (log `text` before the throw) to see the actual status/error element the node returned.","Verify the API key/token field name and value expected by the host's multipart form.","Confirm the host still uses direct_url/download_url element names; update the destination config or host version accordingly.","Retry with a smaller file to rule out size-limit rejections."],"exampleFix":"// before\nconst raw = xmlElement(text, \"direct_url\") ?? xmlElement(text, \"download_url\");\n// host renamed the element:\n// after\nconst raw = xmlElement(text, \"direct_url\") ?? xmlElement(text, \"download_url\") ?? xmlElement(text, \"url\"); // match the host's current schema","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function hasDirectUrl(xml: string): boolean {\n  return /<status>ok<\\/status>/.test(xml) && /<(direct_url|download_url)>[^<]+<\\/(direct_url|download_url)>/.test(xml);\n}","tryCatchPattern":"try {\n  const result = await publish(destination, request);\n} catch (err) {\n  if (err instanceof LegacyDestinationError && err.message.includes(\"direct image URL\")) {\n    logger.warn(\"legacy upload node gave no direct_url; inspecting response\", { destination });\n    // retry with a smaller file or different destination\n  } else throw err;\n}","preventionTips":["log the raw upload-node response body on failure to catch schema drift early","verify the API key field name against current host docs before deploying","test uploads with both small and max-size files","subscribe to the image host's changelog/status feed"],"tags":["blob-upload","http","xml-parsing","api-response"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}