{"record":{"id":"5c3b81ce79fe6bb8","repo":"can1357/oh-my-pi","slug":"upload-rejected-upstreamerror","errorCode":null,"errorMessage":"upload rejected: ${upstreamError}","messagePattern":"upload rejected: (.+?)","errorType":"exception","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":206,"sourceCode":"\nfunction createSulUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst destination = \"s-ul\" as const;\n\tlet apiKey: string;\n\ttry {\n\t\tapiKey = requireCredential(config, \"apiKey\");\n\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}","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L188-L224","documentation":"This LegacyDestinationError is thrown by the s-ul.eu legacy uploader when the upstream server accepted the HTTP request but returned a JSON body containing a non-empty 'error' field. The library surfaces the upstream message verbatim inside 'upload rejected: <upstreamError>' so the real cause (auth failure, quota, bad file) is visible. It means the upload failed on the server side, not in transport.","triggerScenarios":"POSTing multipart data to https://s-ul.eu/api/v1/upload succeeds with a 2xx (expectOk passes) but the JSON response includes an 'error' string — e.g. invalid/missing API key, file too large, or rate limiting wrapped in a 200 response.","commonSituations":"Expired or revoked s-ul API key; uploading a file above the account's size quota; the s-ul service changing its error payload shape while still returning HTTP 200; transient upstream rate limits.","solutions":["Read the embedded upstreamError text and fix the stated cause (most often the apiKey credential)","Verify the apiKey in destination credentials is valid and active on s-ul.eu","Check the file size against the account's upload quota and retry with a smaller file","Retry after a delay if the error indicates rate limiting"],"exampleFix":"// before: stale key fails silently upstream\nconst body = multipartFile(request, \"file\", { wizard: \"true\", key: oldKey, client: \"sharex-native\" });\n// after: validate the key is present and current before uploading\nif (!apiKey || apiKey.length < 32) throw new Error(\"configure a valid s-ul apiKey\");\nconst body = multipartFile(request, \"file\", { wizard: \"true\", key: apiKey, client: \"sharex-native\" });","handlingStrategy":"try-catch","validationCode":"if (typeof apiKey !== \"string\" || !apiKey.trim()) throw new Error(\"s-ul apiKey credential is required before upload\");","typeGuard":"function hasUpstreamError(data: unknown): data is { error: string } {\n\treturn typeof data === \"object\" && data !== null && \"error\" in data && typeof (data as { error: unknown }).error === \"string\" && (data as { error: string }).error.trim().length > 0;\n}","tryCatchPattern":"try {\n\tawait uploader.upload(request);\n} catch (err) {\n\tif (err instanceof Error && err.name === \"LegacyDestinationError\" && /upload rejected:/.test(err.message)) {\n\t\tconst upstream = err.message.split(\"upload rejected: \")[1];\n\t\tlogger.warn(\"s-ul rejected upload\", { upstream });\n\t}\n\tthrow err;\n}","preventionTips":["Keep the s-ul apiKey valid and rotate before expiry","Check file size against the account quota before uploading","Treat any 'error' field in a 200 response as fatal and surface it to logs","Add a periodic key-validity check against the API"],"tags":["upload","upstream-rejection","http","legacy-destination"],"backgroundTag":"upload-rejected-by-upstream","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}