can1357/oh-my-pi · error · LegacyDestinationError

upload rejected: ${upstreamError}

Error message

upload rejected: ${upstreamError}

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-legacy.ts:206

function createSulUploader(config: DestinationRuntimeConfig): BlobUploader {
	const destination = "s-ul" as const;
	let apiKey: string;
	try {
		apiKey = requireCredential(config, "apiKey");
	} catch (error) {
		throw failure(destination, error);
	}
	return {
		destination,
		async upload(request) {
			try {
				const body = multipartFile(request, "file", { wizard: "true", key: apiKey, client: "sharex-native" });
				const response = await fetchFor(config)(SUL_UPLOAD_URL, { method: "POST", body });
				await expectOk(response, destination);
				const data = await jsonObject(destination, response);
				const upstreamError = firstString(data, ["error"]);
				if (upstreamError) throw new LegacyDestinationError(destination, `upload rejected: ${upstreamError}`);
				const protocol = firstString(data, ["protocol"]);
				const domain = firstString(data, ["domain"]);
				const filename = firstString(data, ["filename"]);
				const extension = firstString(data, ["extension"]) ?? "";
				if (!protocol || !domain || !filename) {
					throw new LegacyDestinationError(destination, "the upload response omitted URL components");
				}
				const url = httpUrl(destination, `${protocol}${domain}/${filename}${extension}`);
				const deleteUrl = new URL(SUL_DELETE_URL);
				deleteUrl.searchParams.set("key", apiKey);
				deleteUrl.searchParams.set("file", filename);
				return publication(destination, request, url, {
					delete: { method: "GET", url: deleteUrl.href },
					remoteId: filename,
				});
			} catch (error) {
				throw failure(destination, error);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the embedded upstreamError text and fix the stated cause (most often the apiKey credential)
  2. Verify the apiKey in destination credentials is valid and active on s-ul.eu
  3. Check the file size against the account's upload quota and retry with a smaller file
  4. Retry after a delay if the error indicates rate limiting

Example fix

// before: stale key fails silently upstream
const body = multipartFile(request, "file", { wizard: "true", key: oldKey, client: "sharex-native" });
// after: validate the key is present and current before uploading
if (!apiKey || apiKey.length < 32) throw new Error("configure a valid s-ul apiKey");
const body = multipartFile(request, "file", { wizard: "true", key: apiKey, client: "sharex-native" });
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof apiKey !== "string" || !apiKey.trim()) throw new Error("s-ul apiKey credential is required before upload");

Type guard

function hasUpstreamError(data: unknown): data is { error: string } {
	return typeof data === "object" && data !== null && "error" in data && typeof (data as { error: unknown }).error === "string" && (data as { error: string }).error.trim().length > 0;
}

Try / catch

try {
	await uploader.upload(request);
} catch (err) {
	if (err instanceof Error && err.name === "LegacyDestinationError" && /upload rejected:/.test(err.message)) {
		const upstream = err.message.split("upload rejected: ")[1];
		logger.warn("s-ul rejected upload", { upstream });
	}
	throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/5c3b81ce79fe6bb8. Report an issue: GitHub.