{"record":{"id":"a24638f56689e6db","repo":"can1357/oh-my-pi","slug":"destination-returned-an-invalid-upload-url","errorCode":null,"errorMessage":"${destination} returned an invalid upload URL","messagePattern":"(.+?) returned an invalid upload URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-anonymous.ts","lineNumber":34,"sourceCode":"const UGUU_UPLOAD_URL = \"https://uguu.se/upload?output=text\";\nconst TMPFILES_UPLOAD_URL = \"https://tmpfiles.org/api/v1/upload\";\nconst HOUR_MS = 60 * 60 * 1_000;\nconst DAY_MS = 24 * HOUR_MS;\n\nconst LITTERBOX_TTLS = {\n\t\"1h\": HOUR_MS,\n\t\"12h\": 12 * HOUR_MS,\n\t\"24h\": 24 * HOUR_MS,\n\t\"72h\": 72 * HOUR_MS,\n} as const;\n\nfunction httpUrl(value: string, destination: BlobDestinationId): string {\n\tconst trimmed = value.trim();\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`${destination} returned an invalid upload URL`);\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n\t\tthrow new Error(`${destination} returned an unsupported upload URL`);\n\t}\n\treturn url.href;\n}\n\nasync function uploadTextUrl(\n\tdestination: BlobDestinationId,\n\tconfig: DestinationRuntimeConfig,\n\tendpoint: string,\n\tform: FormData,\n): Promise<{ response: Response; url: string }> {\n\tconst response = await expectOk(await fetchFor(config)(endpoint, { method: \"POST\", body: form }), destination);\n\treturn { response, url: httpUrl(await response.text(), destination) };\n}\n\nfunction remoteName(url: string): string | undefined {","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-anonymous.ts#L16-L52","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Include the scheme in configured URLs: `https://litterbox.catbox.moe/resources/internals/api.php`.","Use absolute URLs for the base/uploadUrl options; relative paths are rejected.","Trim whitespace/newlines from config values (the function trims but leading garbage before a scheme still fails).","Verify the host is actually returning JSON with the expected URL field, not an error page."],"exampleFix":"// before\n{ \"options\": { \"base\": \"litterbox.catbox.moe\" } }\n// after\n{ \"options\": { \"base\": \"https://litterbox.catbox.moe\" } }","handlingStrategy":"validation","validationCode":"function isHttpUrl(v: unknown): boolean {\n  if (typeof v !== \"string\") return false;\n  try { const u = new URL(v.trim()); return u.protocol === \"http:\" || u.protocol === \"https:\"; } catch { return false; }\n}\nif (!isHttpUrl(config.options.base)) throw new Error(\"base must be an absolute http(s) URL\");","typeGuard":"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; } };","tryCatchPattern":"try {\n  uploader = createUploader(config);\n} catch (err) {\n  if (String(err).includes(\"invalid upload URL\")) {\n    logger.warn(\"check base/uploadUrl: must be absolute http(s) URL\", { err: String(err) });\n  } else throw err;\n}","preventionTips":["Always include the scheme (https://) in configured URLs.","Validate config URLs at load time with new URL() and a protocol check.","When a host returns the URL, verify the response is JSON with the expected field before treating it as a URL."],"tags":["config","url","validation"],"backgroundTag":"invalid-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}