can1357/oh-my-pi · error

${destination} returned an invalid upload URL

Error message

${destination} returned an invalid upload URL

What it means

httpUrl parses a URL string returned by (or configured for) a file host and throws when it cannot be parsed by the URL constructor. The library validates that upload URLs are well-formed before using them in requests, failing fast with a destination-specific message instead of an opaque fetch error later.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-anonymous.ts:34

const UGUU_UPLOAD_URL = "https://uguu.se/upload?output=text";
const TMPFILES_UPLOAD_URL = "https://tmpfiles.org/api/v1/upload";
const HOUR_MS = 60 * 60 * 1_000;
const DAY_MS = 24 * HOUR_MS;

const LITTERBOX_TTLS = {
	"1h": HOUR_MS,
	"12h": 12 * HOUR_MS,
	"24h": 24 * HOUR_MS,
	"72h": 72 * HOUR_MS,
} as const;

function httpUrl(value: string, destination: BlobDestinationId): string {
	const trimmed = value.trim();
	let url: URL;
	try {
		url = new URL(trimmed);
	} catch {
		throw new Error(`${destination} returned an invalid upload URL`);
	}
	if (url.protocol !== "http:" && url.protocol !== "https:") {
		throw new Error(`${destination} returned an unsupported upload URL`);
	}
	return url.href;
}

async function uploadTextUrl(
	destination: BlobDestinationId,
	config: DestinationRuntimeConfig,
	endpoint: string,
	form: FormData,
): Promise<{ response: Response; url: string }> {
	const response = await expectOk(await fetchFor(config)(endpoint, { method: "POST", body: form }), destination);
	return { response, url: httpUrl(await response.text(), destination) };
}

function remoteName(url: string): string | undefined {

View on GitHub (pinned to 9690622007)

Solutions

  1. Include the scheme in configured URLs: `https://litterbox.catbox.moe/resources/internals/api.php`.
  2. Use absolute URLs for the base/uploadUrl options; relative paths are rejected.
  3. Trim whitespace/newlines from config values (the function trims but leading garbage before a scheme still fails).
  4. Verify the host is actually returning JSON with the expected URL field, not an error page.

Example fix

// before
{ "options": { "base": "litterbox.catbox.moe" } }
// after
{ "options": { "base": "https://litterbox.catbox.moe" } }
Defensive patterns

Strategy: validation

Validate before calling

function isHttpUrl(v: unknown): boolean {
  if (typeof v !== "string") return false;
  try { const u = new URL(v.trim()); return u.protocol === "http:" || u.protocol === "https:"; } catch { return false; }
}
if (!isHttpUrl(config.options.base)) throw new Error("base must be an absolute http(s) URL");

Type guard

const parseHttpUrl = (v: string): URL | null => { try { const u = new URL(v.trim()); return u.protocol === "http:" || u.protocol === "https:" ? u : null; } catch { return null; } };

Try / catch

try {
  uploader = createUploader(config);
} catch (err) {
  if (String(err).includes("invalid upload URL")) {
    logger.warn("check base/uploadUrl: must be absolute http(s) URL", { err: String(err) });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling httpUrl — via uploadTextUrl, absolutePomfUrl, uploadUrl, base, or response handling — when the value is not a parseable absolute URL: empty string, relative path like "/upload", text/html error page body accidentally used as URL, or a URL with spaces/control characters.

Common situations: Misconfigured base option ("example.com" without scheme), a host returning an HTML error page where JSON was expected and a field got used as the URL, or a proxy rewriting responses so the URL field is empty.

Related errors


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