can1357/oh-my-pi · error · LegacyDestinationError

the replacement endpoint returned no direct URL

Error message

the replacement endpoint returned no direct URL

What it means

Thrown when the no-credential replacement uploader receives an empty response body after a successful HTTP status. The endpoint is expected to return the direct URL as plain text; an empty body means nothing was uploaded or the endpoint failed silently, so no publication can be created.

Source

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

				throw failure(destination, error);
			}
		},
	};
}

function createTransferUploader(config: DestinationRuntimeConfig, endpoint: URL): BlobUploader {
	const destination = "transfer-sh" as const;
	return {
		destination,
		async upload(request) {
			try {
				const response = await fetchFor(config)(endpoint, {
					method: "POST",
					body: multipartFile(request),
				});
				await expectOk(response, destination);
				const raw = (await response.text()).trim();
				if (!raw) throw new LegacyDestinationError(destination, "the replacement endpoint returned no direct URL");
				const deleteUrl = response.headers.get("x-url-delete")?.trim();
				return publication(
					destination,
					request,
					httpUrl(destination, raw, endpoint),
					deleteUrl ? { delete: { method: "DELETE", url: httpUrl(destination, deleteUrl, endpoint) } } : undefined,
				);
			} catch (error) {
				throw failure(destination, error);
			}
		},
	};
}

async function discoverSendSpaceNode(config: DestinationRuntimeConfig, endpoint: URL): Promise<SendSpaceNode> {
	const destination = "sendspace" as const;
	const discovery = new URL(endpoint);
	discovery.searchParams.set("method", "anonymous.uploadGetInfo");

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the endpoint URL points at the actual upload API route, not a root/health path
  2. Test the endpoint manually (curl -F file=@img.png) to see what it returns
  3. Check whether the service is deprecated/shutting down and choose another replacement endpoint
  4. Inspect for a proxy layer stripping response bodies

Example fix

// before: endpoint root, returns empty 200
endpoint: "https://transfer-sh.example/"
// after: exact upload route that echoes the URL
endpoint: "https://transfer-sh.example/upload"
Defensive patterns

Strategy: validation

Validate before calling

// smoke-test the endpoint before relying on it
const probe = await fetch(endpoint, { method: "POST", body: tinyMultipart });
const text = (await probe.text()).trim();
if (!text) throw new Error(`endpoint ${endpoint} returned an empty body`);

Try / catch

try {
	await uploader.upload(request);
} catch (err) {
	if (err instanceof Error && /returned no direct URL/.test(err.message)) {
		// endpoint returned 2xx with empty body — switch endpoints or probe manually
	}
	throw err;
}

Prevention

When it happens

Trigger: POST multipart 'file' (no api key) to the configured endpoint; expectOk passes but response.text().trim() is '' — the server returned 2xx with an empty body.

Common situations: Endpoint misconfigured to a health-check or root URL that 200s with no body; service soft-failing with an empty 200; a reverse proxy swallowing the response; the service shutting down but still answering 200.

Related errors


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