can1357/oh-my-pi · error · DestinationUnavailableError

${protocol.toUpperCase()} requires options.commandBinary poi

Error message

${protocol.toUpperCase()} requires options.commandBinary pointing to curl

What it means

A DestinationUnavailableError thrown when the ftp/ftps destination (non-sftp path, which shells out to curl) has no options.commandBinary configured. Because the broker does not bundle an FTP client, it requires an explicit path/binary name for curl and refuses to guess one.

Source

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

		const connectionName = `blob-${username}-${host}-${port}`.replace(/[^A-Za-z0-9._-]/g, "-");
		return {
			destination: "ftp",
			async upload(request) {
				const filename = safeFileName(request);
				await writeRemoteFile(
					{ name: connectionName, host, username, port, ...(keyPath ? { keyPath } : {}) },
					remotePath(directory, filename),
					request.bytes,
					{},
				);
				return publication("ftp", request, publicUrl(publicBase, directory, filename));
			},
		};
	}

	const binary = optionString(config, "commandBinary");
	if (!binary) {
		throw new DestinationUnavailableError(
			"ftp",
			`${protocol.toUpperCase()} requires options.commandBinary pointing to curl`,
		);
	}
	const password = credentialString(config, "password") ?? "";
	const port = optionNumber(config, "port", 21) ?? 21;
	return {
		destination: "ftp",
		async upload(request) {
			const filename = safeFileName(request);
			const destinationPath = remotePath(directory, filename);
			const args = [
				binary,
				"--fail",
				"--silent",
				"--show-error",
				"--ftp-create-dirs",
				"--upload-file",

View on GitHub (pinned to 9690622007)

Solutions

  1. Set options.commandBinary to your curl executable, e.g. "/usr/bin/curl" or "curl" if on PATH.
  2. Install curl in the environment running the upload (apt-get install curl / apk add curl).
  3. Verify curl supports FTP (--ftp-create-dirs, --upload-file) — standard builds do; the binary is invoked with --fail --silent --show-error.
  4. If you actually want SFTP, set options.protocol to "sftp" instead, which uses the built-in SSH transport and needs no curl.

Example fix

// before
{ "destination": "ftp", "options": { "protocol": "ftps", "host": "ftp.example.com", "publicBaseUrl": "https://f.example.com" } }
// after
{ "destination": "ftp", "options": { "protocol": "ftps", "host": "ftp.example.com", "commandBinary": "/usr/bin/curl", "publicBaseUrl": "https://f.example.com" } }
Defensive patterns

Strategy: validation

Validate before calling

if (['ftp', 'ftps'].includes(dest.options?.protocol ?? 'ftp') && !dest.options?.commandBinary) {
  throw new Error('ftp/ftps destinations require options.commandBinary pointing to curl');
}

Try / catch

try {
  const uploader = createSelfHostedUploader('ftp', config);
} catch (err) {
  if (err?.name === 'DestinationUnavailableError' && /requires options\.commandBinary/.test(err.message)) {
    // install curl or set commandBinary, then re-create the destination
  } else throw err;
}

Prevention

When it happens

Trigger: Destination "ftp" with options.protocol "ftp" or "ftps" (or protocol unset resolves only for sftp — here protocol is ftp/ftps) and options.commandBinary missing, null, or empty string.

Common situations: New self-hosted FTP destinations where the curl requirement was missed; minimal containers/CI images without curl installed; users assuming a system ftp client will be used automatically.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — 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/96c192231fd56444. Report an issue: GitHub.