{"record":{"id":"c356388aed9e519c","repo":"can1357/oh-my-pi","slug":"the-upload-response-omitted-url-components","errorCode":null,"errorMessage":"the upload response omitted URL components","messagePattern":"the upload response omitted URL components","errorType":"exception","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":212,"sourceCode":"\t} catch (error) {\n\t\tthrow failure(destination, error);\n\t}\n\treturn {\n\t\tdestination,\n\t\tasync upload(request) {\n\t\t\ttry {\n\t\t\t\tconst body = multipartFile(request, \"file\", { wizard: \"true\", key: apiKey, client: \"sharex-native\" });\n\t\t\t\tconst response = await fetchFor(config)(SUL_UPLOAD_URL, { method: \"POST\", body });\n\t\t\t\tawait expectOk(response, destination);\n\t\t\t\tconst data = await jsonObject(destination, response);\n\t\t\t\tconst upstreamError = firstString(data, [\"error\"]);\n\t\t\t\tif (upstreamError) throw new LegacyDestinationError(destination, `upload rejected: ${upstreamError}`);\n\t\t\t\tconst protocol = firstString(data, [\"protocol\"]);\n\t\t\t\tconst domain = firstString(data, [\"domain\"]);\n\t\t\t\tconst filename = firstString(data, [\"filename\"]);\n\t\t\t\tconst extension = firstString(data, [\"extension\"]) ?? \"\";\n\t\t\t\tif (!protocol || !domain || !filename) {\n\t\t\t\t\tthrow new LegacyDestinationError(destination, \"the upload response omitted URL components\");\n\t\t\t\t}\n\t\t\t\tconst url = httpUrl(destination, `${protocol}${domain}/${filename}${extension}`);\n\t\t\t\tconst deleteUrl = new URL(SUL_DELETE_URL);\n\t\t\t\tdeleteUrl.searchParams.set(\"key\", apiKey);\n\t\t\t\tdeleteUrl.searchParams.set(\"file\", filename);\n\t\t\t\treturn publication(destination, request, url, {\n\t\t\t\t\tdelete: { method: \"GET\", url: deleteUrl.href },\n\t\t\t\t\tremoteId: filename,\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 createPuushUploader(config: DestinationRuntimeConfig, endpoint: URL): BlobUploader {\n\tconst destination = \"puush\" as const;","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L194-L230","documentation":"Thrown when the s-ul.eu upload endpoint returns 200 with no 'error' field, but the JSON is missing one or more of the required URL components: protocol, domain, or filename. The uploader builds the final public URL from these fields, so an incomplete response cannot be published. It indicates an unexpected or partially-failed upstream response.","triggerScenarios":"POST to SUL_UPLOAD_URL returns valid JSON lacking protocol/domain/filename keys — e.g. a success acknowledgement without URL data, an HTML error page parsed as JSON-ish, or a schema change on the s-ul side.","commonSituations":"s-ul.eu API schema drift; proxy/interception middleware stripping or rewriting response bodies; the service degrading and returning truncated success payloads.","solutions":["Log the raw response body to see what the server actually returned","Retry the upload — transient upstream degradation often produces incomplete payloads","Check s-ul.eu API status/docs for schema changes and update the integration","If persistent, switch the destination to a replacement endpoint configuration"],"exampleFix":"// before: assume every response has URL components\nconst data = await jsonObject(destination, response);\n// after: log-and-retry on incomplete payloads\nconst data = await jsonObject(destination, response);\nif (!firstString(data, [\"protocol\"]) || !firstString(data, [\"domain\"]) || !firstString(data, [\"filename\"])) {\n\tthrow new RetryableUploadError(`incomplete response: ${JSON.stringify(data)}`);\n}","handlingStrategy":"validation","validationCode":"function looksLikeSulUploadResponse(data: unknown): boolean {\n\tif (typeof data !== \"object\" || data === null) return false;\n\tconst r = data as Record<string, unknown>;\n\treturn typeof r.protocol === \"string\" && typeof r.domain === \"string\" && typeof r.filename === \"string\";\n}\n// call after parsing, before relying on the URL","typeGuard":"function isCompleteSulResponse(d: unknown): d is { protocol: string; domain: string; filename: string; extension?: string } {\n\tconst r = d as Record<string, unknown>;\n\treturn typeof r?.protocol === \"string\" && r.protocol.length > 0 && typeof r?.domain === \"string\" && r.domain.length > 0 && typeof r?.filename === \"string\" && r.filename.length > 0;\n}","tryCatchPattern":"try {\n\treturn await uploader.upload(request);\n} catch (err) {\n\tif (err instanceof Error && /omitted URL components/.test(err.message)) {\n\t\t// retry once or fall back to another destination\n\t\treturn fallbackUploader.upload(request);\n\t}\n\tthrow err;\n}","preventionTips":["Log raw response bodies on upload failure to detect schema drift early","Pin a known-good replacement endpoint rather than relying on a public service's undocumented schema","Monitor the upstream service for API changes","Implement a fallback destination for critical publications"],"tags":["upload","response-parsing","http","legacy-destination"],"backgroundTag":"incomplete-upload-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}