can1357/oh-my-pi · error · LegacyDestinationError
the upload node did not return a direct image URL
Error message
the upload node did not return a direct image URL
What it means
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.
Source
Thrown at packages/coding-agent/src/blob-broker/uploaders-legacy.ts:460
function createSendSpaceUploader(config: DestinationRuntimeConfig, endpoint: URL): BlobUploader {
const destination = "sendspace" as const;
return {
destination,
async upload(request: BlobUploadRequest) {
try {
const node = await discoverSendSpaceNode(config, endpoint);
const body = multipartFile(request, "userfile", {
MAX_FILE_SIZE: node.maxFileSize,
UPLOAD_IDENTIFIER: node.uploadIdentifier,
extra_info: node.extraInfo,
});
const response = await fetchFor(config)(node.url, { method: "POST", body });
await expectOk(response, destination);
const text = await response.text();
const status = xmlElement(text, "status");
const raw = xmlElement(text, "direct_url") ?? xmlElement(text, "download_url");
if (status !== "ok" || !raw) {
throw new LegacyDestinationError(destination, "the upload node did not return a direct image URL");
}
const deleteUrl = xmlElement(text, "delete_url");
return publication(
destination,
request,
httpUrl(destination, raw),
deleteUrl ? { delete: { method: "GET", url: httpUrl(destination, deleteUrl) } } : undefined,
);
} catch (error) {
throw failure(destination, error);
}
},
};
}
function incompatible(destination: BlobDestinationId, reason: string): never {
throw new DestinationUnavailableError(destination, reason);
}View on GitHub (pinned to 9690622007)
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.
Example fix
// before const raw = xmlElement(text, "direct_url") ?? xmlElement(text, "download_url"); // host renamed the element: // after const raw = xmlElement(text, "direct_url") ?? xmlElement(text, "download_url") ?? xmlElement(text, "url"); // match the host's current schema
Defensive patterns
Strategy: try-catch
Type guard
function hasDirectUrl(xml: string): boolean {
return /<status>ok<\/status>/.test(xml) && /<(direct_url|download_url)>[^<]+<\/(direct_url|download_url)>/.test(xml);
} Try / catch
try {
const result = await publish(destination, request);
} catch (err) {
if (err instanceof LegacyDestinationError && err.message.includes("direct image URL")) {
logger.warn("legacy upload node gave no direct_url; inspecting response", { destination });
// retry with a smaller file or different destination
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- the discovery endpoint returned incomplete upload-node metad
- Gemini Files API upload initialization failed with HTTP ${st
- Destination option ${optionName} must use http or https
- V2 remote compaction failed (${response.status} ${response.s
- No response body for V2 compaction streaming
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4e945e97b143700a.
Report an issue: GitHub.