{"record":{"id":"5712bc5a86fc030d","repo":"can1357/oh-my-pi","slug":"destination-option-optionname-must-use-http-or","errorCode":null,"errorMessage":"Destination option ${optionName} must use http or https","messagePattern":"Destination option (.+?) must use http or https","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts","lineNumber":137,"sourceCode":"}\n\nfunction endpoint(base: string, ...parts: string[]): string {\n\tconst url = new URL(base);\n\turl.pathname = `${url.pathname.replace(/\\/+$/, \"\")}/${encodedPath(parts)}`;\n\turl.search = \"\";\n\turl.hash = \"\";\n\treturn url.toString();\n}\n\nfunction httpBase(value: string, optionName: string): URL {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new Error(`Destination option ${optionName} must be an absolute HTTP URL`);\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n\t\tthrow new Error(`Destination option ${optionName} must use http or https`);\n\t}\n\treturn url;\n}\n\nfunction publicUrl(baseValue: string, directory: string | undefined, filename: string): string {\n\tconst url = httpBase(baseValue, \"publicBaseUrl\");\n\tconst relative = encodedPath([...pathParts(directory), filename]);\n\turl.pathname = `${url.pathname.replace(/\\/+$/, \"\")}/${relative}`;\n\turl.hash = \"\";\n\treturn url.toString();\n}\n\nfunction basicAuthorization(username: string, password: string): string {\n\treturn `Basic ${Buffer.from(`${username}:${password}`, \"utf8\").toString(\"base64\")}`;\n}\n\nfunction expiry(days: number | undefined): { expiresAt?: number; expireDate?: string } {\n\tif (days === undefined || days <= 0) return {};","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts#L119-L155","documentation":"Thrown by httpBase() when a destination option expected to be a base URL parses as a valid absolute URL but its protocol is neither http: nor https:. The blob-broker requires every self-hosted destination base/public URL to point at an HTTP(S) endpoint so uploads and generated public links are well-formed. Non-HTTP schemes (ftp:, sftp:, file:, ws:) are rejected at uploader creation time.","triggerScenarios":"Passing options.publicBaseUrl (ftp/shared-folder), options.host (owncloud), or options.endpoint/apiUrl (plik/seafile) with a scheme other than http or https, e.g. \"ftp://mirror.example.com\", \"localhost:8080\" normalized oddly, or a bare \"file:///srv/share\".","commonSituations":"Copy-pasting an FTP/SFTP host into publicBaseUrl when configuring the ftp destination; using a ws:// or file:// URL; forgetting that publicBaseUrl must be where end users browse, not the transfer protocol; typos like http// (which fails earlier at 850's sibling 'absolute HTTP URL' throw).","solutions":["Set the option to an absolute URL starting with http:// or https://, e.g. https://files.example.com/share.","If the underlying transfer really is FTP, keep the ftp:// URL only in options.host/transfer settings and give publicBaseUrl an https:// web address.","Verify the option name you set matches what the destination expects (publicBaseUrl vs host vs apiUrl) — you may have put the URL in the wrong key.","Recreate or reload the destination after fixing the config; the check runs once at uploader creation (createSelfHostedUploader)."],"exampleFix":"// before\n{ \"destination\": \"shared-folder\", \"options\": { \"root\": \"/srv/share\", \"publicBaseUrl\": \"file:///srv/share\" } }\n// after\n{ \"destination\": \"shared-folder\", \"options\": { \"root\": \"/srv/share\", \"publicBaseUrl\": \"https://files.example.com/share\" } }","handlingStrategy":"validation","validationCode":"function isHttpBase(value) {\n  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}\nif (!isHttpBase(config.publicBaseUrl)) throw new Error('publicBaseUrl must be an http(s) URL');","typeGuard":null,"tryCatchPattern":"try {\n  await uploader.upload(request);\n} catch (err) {\n  if (err instanceof Error && /must use http or https/.test(err.message)) {\n    // surface a config-validation failure to the operator before retrying\n  } else throw err;\n}","preventionTips":["Validate every base-URL option with new URL() plus a protocol allowlist at config load time.","Keep transfer-protocol URLs (ftp://) separate from public web URLs (https://) in your schema.","Add a config schema check that rejects non-http(s) schemes for *BaseUrl/host/endpoint keys.","Normalize options early: strip trailing slashes and require absolute URLs."],"tags":["configuration","url-validation","http","blob-upload"],"backgroundTag":"invalid-url-protocol","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}