can1357/oh-my-pi · error · Error

Destination option ${optionName} must use http or https

Error message

Destination option ${optionName} must use http or https

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts:137

}

function endpoint(base: string, ...parts: string[]): string {
	const url = new URL(base);
	url.pathname = `${url.pathname.replace(/\/+$/, "")}/${encodedPath(parts)}`;
	url.search = "";
	url.hash = "";
	return url.toString();
}

function httpBase(value: string, optionName: string): URL {
	let url: URL;
	try {
		url = new URL(value);
	} catch {
		throw new Error(`Destination option ${optionName} must be an absolute HTTP URL`);
	}
	if (url.protocol !== "http:" && url.protocol !== "https:") {
		throw new Error(`Destination option ${optionName} must use http or https`);
	}
	return url;
}

function publicUrl(baseValue: string, directory: string | undefined, filename: string): string {
	const url = httpBase(baseValue, "publicBaseUrl");
	const relative = encodedPath([...pathParts(directory), filename]);
	url.pathname = `${url.pathname.replace(/\/+$/, "")}/${relative}`;
	url.hash = "";
	return url.toString();
}

function basicAuthorization(username: string, password: string): string {
	return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
}

function expiry(days: number | undefined): { expiresAt?: number; expireDate?: string } {
	if (days === undefined || days <= 0) return {};

View on GitHub (pinned to 9690622007)

Solutions

  1. Set the option to an absolute URL starting with http:// or https://, e.g. https://files.example.com/share.
  2. If the underlying transfer really is FTP, keep the ftp:// URL only in options.host/transfer settings and give publicBaseUrl an https:// web address.
  3. 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.
  4. Recreate or reload the destination after fixing the config; the check runs once at uploader creation (createSelfHostedUploader).

Example fix

// before
{ "destination": "shared-folder", "options": { "root": "/srv/share", "publicBaseUrl": "file:///srv/share" } }
// after
{ "destination": "shared-folder", "options": { "root": "/srv/share", "publicBaseUrl": "https://files.example.com/share" } }
Defensive patterns

Strategy: validation

Validate before calling

function isHttpBase(value) {
  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; }
  catch { return false; }
}
if (!isHttpBase(config.publicBaseUrl)) throw new Error('publicBaseUrl must be an http(s) URL');

Try / catch

try {
  await uploader.upload(request);
} catch (err) {
  if (err instanceof Error && /must use http or https/.test(err.message)) {
    // surface a config-validation failure to the operator before retrying
  } else throw err;
}

Prevention

When it happens

Trigger: 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".

Common situations: 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).

Related errors


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