can1357/oh-my-pi · error · Error

Destination option ${key} must be a non-empty string

Error message

Destination option ${key} must be a non-empty string

What it means

Thrown by requiredStringOption when a destination config option (fetched via requireOption) is present but is not a string, or is a string that is empty/whitespace-only. Self-hosted uploaders call it for every mandatory option (host, publicBase, root, apiUrl, repositoryId, base), so a bad option value fails fast at uploader creation.

Source

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

}

function plikUpload(value: unknown): PlikUpload {
	if (typeof value !== "object" || value === null || !("id" in value) || !("uploadToken" in value)) {
		throw new Error("plik upload metadata did not include an id and upload token");
	}
	const id = identifier(value.id);
	const uploadToken = nonEmptyString(value.uploadToken);
	if (!id || !uploadToken) throw new Error("plik upload metadata did not include an id and upload token");
	let downloadBase: string | undefined;
	if ("downloadURL" in value) downloadBase = nonEmptyString(value.downloadURL);
	if (!downloadBase && "downloadDomain" in value) downloadBase = nonEmptyString(value.downloadDomain);
	return { id, uploadToken, ...(downloadBase ? { downloadBase } : {}) };
}

function requiredStringOption(config: DestinationRuntimeConfig, key: string): string {
	const value = requireOption(config, key);
	if (typeof value !== "string" || value.trim() === "") {
		throw new Error(`Destination option ${key} must be a non-empty string`);
	}
	return value.trim();
}

function pathParts(value: string | undefined): string[] {
	if (!value) return [];
	const parts = value.replaceAll("\\", "/").split("/");
	const result: string[] = [];
	for (const part of parts) {
		if (!part || part === ".") continue;
		if (part === ".." || part.includes("\0"))
			throw new Error("Destination paths cannot contain parent traversal or NUL bytes");
		result.push(part);
	}
	return result;
}

function safeFileName(request: BlobUploadRequest): string {

View on GitHub (pinned to 9690622007)

Solutions

  1. Open the destination config and set the named option (shown in the error message) to a real non-empty string
  2. Check where the value comes from — an unset env variable interpolating to "" is the usual culprit
  3. Quote string values in YAML/JSON so numbers/booleans don't leak in (e.g. port: "22" not port: 22)
  4. Validate the full destination config before saving it (run each required option through typeof v === 'string' && v.trim() !== '')

Example fix

// before
{ "type": "webdav", "host": "", "root": 42 }
// after
{ "type": "webdav", "host": "cloud.example.com", "root": "/remote.php/webdav" }
Defensive patterns

Strategy: validation

Validate before calling

function requireNonEmptyString(config: Record<string, unknown>, key: string): string {
  const v = config[key];
  if (typeof v !== "string" || v.trim() === "") throw new Error(`Destination option ${key} must be a non-empty string (got ${JSON.stringify(v)})`);
  return v.trim();
}
// validate all required keys before creating the destination
for (const k of ["host", "root", "publicBase"]) requireNonEmptyString(destConfig, k);

Type guard

function isFilledString(value: unknown): value is string {
  return typeof value === "string" && value.trim().length > 0;
}

Try / catch

try {
  const uploader = createWebdavUploader(dest);
} catch (err) {
  const m = (err as Error).message.match(/Destination option (\S+) must be a non-empty string/);
  if (m) throw new Error(`Fix your destination config: option '${m[1]}' is missing or empty`);
  throw err;
}

Prevention

When it happens

Trigger: Creating any self-hosted blob destination (webdav, ftp, sftp, gitea, etc.) where a required option is set to an empty string, a number, boolean, null, or whitespace — e.g. `"host": ""` or `"apiUrl": 8080` in the destination config.

Common situations: Empty env-var interpolation in config (HOST= empty), YAML/JSON type coercion turning a value into a number, copy-pasting a config template without filling fields, or trimming leaving only whitespace.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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