{"record":{"id":"3c5b1e7327085e86","repo":"can1357/oh-my-pi","slug":"the-discovery-endpoint-returned-incomplete-upload","errorCode":null,"errorMessage":"the discovery endpoint returned incomplete upload-node metadata","messagePattern":"the discovery endpoint returned incomplete upload-node metadata","errorType":"exception","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":432,"sourceCode":"\tconst discovery = new URL(endpoint);\n\tdiscovery.searchParams.set(\"method\", \"anonymous.uploadGetInfo\");\n\tdiscovery.searchParams.set(\"speed_limit\", \"0\");\n\tdiscovery.searchParams.set(\"api_version\", \"1.0\");\n\tdiscovery.searchParams.set(\"app_version\", \"1.0\");\n\tconst apiKey = credentialString(config, \"apiKey\");\n\tif (apiKey) discovery.searchParams.set(\"api_key\", apiKey);\n\tconst response = await fetchFor(config)(discovery, { method: \"GET\" });\n\tawait expectOk(response, destination);\n\tconst xml = await response.text();\n\tif (/\\bstatus=(?:\"fail\"|'fail')/i.test(xml)) {\n\t\tthrow new LegacyDestinationError(destination, \"the discovery endpoint rejected the upload request\");\n\t}\n\tconst rawUrl = xmlAttribute(xml, \"upload\", \"url\");\n\tconst maxFileSize = xmlAttribute(xml, \"upload\", \"max_file_size\");\n\tconst uploadIdentifier = xmlAttribute(xml, \"upload\", \"upload_identifier\");\n\tconst extraInfo = xmlAttribute(xml, \"upload\", \"extra_info\");\n\tif (!rawUrl || !maxFileSize || !uploadIdentifier || !extraInfo) {\n\t\tthrow new LegacyDestinationError(destination, \"the discovery endpoint returned incomplete upload-node metadata\");\n\t}\n\treturn {\n\t\turl: new URL(httpUrl(destination, rawUrl)),\n\t\tmaxFileSize,\n\t\tuploadIdentifier,\n\t\textraInfo,\n\t};\n}\n\nfunction 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,","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L414-L450","documentation":"This DestinationUnavailableError is thrown when the ShareX-legacy discovery endpoint responds, but the XML <upload> node is missing one or more of the required attributes: url, max_file_size, upload_identifier, or extra_info. The library requires a fully-specified upload node to construct a viable legacy uploader, and refuses to proceed with partial metadata.","triggerScenarios":"The destination's discovery URL returns XML whose <upload> element lacks any of the required attributes (url, max_file_size, upload_identifier, extra_info), typically because the remote custom uploader endpoint changed its schema or returned an error page/HTML instead of the expected ShareX XML config.","commonSituations":"Self-hosted image host upgraded or replaced its ShareX-compatible endpoint; destination URL points at a login page or HTML error page instead of the XML config; the host omits extra_info or upload_identifier because it uses an older ShareX config dialect.","solutions":["Verify the discovery URL returns ShareX-style XML with all four attributes on the <upload> node (curl the URL and inspect).","Check the image host's documentation for the correct custom-uploader JSON/XML endpoint path.","Confirm the destination config's URL is not redirected to an HTML login or error page (check with curl -L).","If the host genuinely omits a field, switch the destination to a different uploader family (S3/B2/object storage) supported by the broker."],"exampleFix":"// before\ndiscoveryUrl: \"https://host.example.com/\" // returns HTML landing page\n// after\ndiscoveryUrl: \"https://host.example.com/sharex-config.xml\" // returns XML with <upload url=... max_file_size=... upload_identifier=... extra_info=...>","handlingStrategy":"validation","validationCode":"const xml = await Bun.file(discoveryUrl).text();\nconst required = [\"url\", \"max_file_size\", \"upload_identifier\", \"extra_info\"];\nconst upload = xml.match(/<upload\\b[^>]*>/)?.[0];\nif (!upload || !required.every((attr) => new RegExp(`${attr}=\"[^\"]+\"`).test(upload))) {\n  throw new Error(\"discovery endpoint missing required <upload> attributes\");\n}","typeGuard":"function hasUploadAttributes(xml: string): boolean {\n  const node = /<upload\\b[^>]*>/.exec(xml)?.[0];\n  return !!node && [\"url\", \"max_file_size\", \"upload_identifier\", \"extra_info\"]\n    .every((a) => node.includes(`${a}=\"`));\n}","tryCatchPattern":"try {\n  await publish(destination, request);\n} catch (err) {\n  if (err instanceof LegacyDestinationError && err.message.includes(\"incomplete upload-node metadata\")) {\n    // fall back to another destination or surface a config error to the user\n  } else throw err;\n}","preventionTips":["curl the discovery URL after any host upgrade and diff the <upload> attributes","pin the image host to a version known to emit full ShareX XML","monitor for HTML responses (login/error pages) at the discovery URL","prefer broker destinations with explicit config (S3/B2) over screen-scraped discovery when possible"],"tags":["blob-upload","http","xml-parsing","configuration"],"backgroundTag":"incomplete-api-response-metadata","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}