{"record":{"id":"695c1539121bbf45","repo":"can1357/oh-my-pi","slug":"destination-returned-an-invalid-image-url","errorCode":null,"errorMessage":"${destination} returned an invalid image URL","messagePattern":"(.+?) returned an invalid image URL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-image-hosts.ts","lineNumber":68,"sourceCode":"\tdestination: BlobDestinationId,\n): Record<string, unknown> {\n\treturn responseRecord(record[key], destination);\n}\n\nfunction requiredString(record: Record<string, unknown>, key: string, destination: BlobDestinationId): string {\n\tconst value = record[key];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`${destination} response did not include ${key}`);\n\t}\n\treturn value;\n}\n\nfunction directUrl(value: string, destination: BlobDestinationId): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new Error(`${destination} returned an invalid image URL`);\n\t}\n\tif (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n\t\tthrow new Error(`${destination} returned an invalid image URL`);\n\t}\n\treturn url.href;\n}\n\nfunction createImgurUploader(config: DestinationRuntimeConfig): BlobUploader {\n\tconst accessToken = credentialString(config, \"accessToken\");\n\tconst authorization = accessToken ? `Bearer ${accessToken}` : `Client-ID ${requireCredential(config, \"clientId\")}`;\n\tconst album = optionString(config, \"album\");\n\n\treturn {\n\t\tdestination: \"imgur\",\n\t\tasync upload(request) {\n\t\t\tconst fields: Record<string, string> = {};\n\t\t\tif (album) fields.album = album;\n\t\t\tconst response = await fetchFor(config)(IMGUR_UPLOAD_URL, {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-image-hosts.ts#L50-L86","documentation":"directUrl parses a value returned by the image host into a URL object; if the string is not a syntactically valid absolute URL, this error is thrown. This is the parse-failure branch of URL validation for destination-issued image links.","triggerScenarios":"The host returns a relative path, an empty string, or arbitrary text where a URL is expected, and new URL(value) throws.","commonSituations":"Host returns a path like /images/123 instead of an absolute URL; mocked responses with placeholder strings; schema changes moving the real URL elsewhere so a non-URL field is read.","solutions":["Log the offending value to see what the host returned for the URL field","Prepend the host base URL if the API now returns relative paths","Update the uploader if the URL moved to a different response field","Validate URLs with a guard before passing them into the broker"],"exampleFix":"// before\nconst url = result.path; // \"/images/abc.png\"\n// after\nconst url = new URL(result.path, \"https://host.example\").href;","handlingStrategy":"validation","validationCode":"function isHttpUrl(value) {\n  try {\n    const u = new URL(value);\n    return u.protocol === \"https:\" || u.protocol === \"http:\";\n  } catch { return false; }\n}\nif (!isHttpUrl(hostResponse.url)) throw new Error(\"host returned a non-URL value\");","typeGuard":"function isUrlString(value) {\n  return typeof value === \"string\" && URL.canParse(value);\n}","tryCatchPattern":"try {\n  const pub = await broker.publish(request);\n} catch (err) {\n  if (err.message.endsWith(\"returned an invalid image URL\")) {\n    logger.warn(\"host issued unusable image URL\", { raw: hostResponse.url });\n    return null;\n  }\n  throw err;\n}","preventionTips":["Assert absolute http(s) URLs when integrating a new host","Handle hosts that return relative paths by prefixing their base URL","Sanitize host responses rather than trusting the URL field blindly","Keep mocks/fixtures realistic (absolute https URLs) in tests"],"tags":["url-parsing","validation","image-host"],"backgroundTag":"invalid-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}