can1357/oh-my-pi · error · Error
Destination option protocol must be ftp, ftps, or sftp
Error message
Destination option protocol must be ftp, ftps, or sftp
What it means
Thrown by createFtpUploader when the ftp destination's options.protocol is set to a value other than "ftp", "ftps", or "sftp". The broker supports exactly three transfer protocols for the ftp destination id; anything else (including case variants like "FTP" or "SFTP") is rejected before any upload attempt.
Source
Thrown at packages/coding-agent/src/blob-broker/uploaders-self-hosted.ts:178
function errorCode(error: unknown): unknown {
if (typeof error !== "object" || error === null || !("code" in error)) return undefined;
return error.code;
}
function ftpUploadUrl(protocol: "ftp" | "ftps", host: string, port: number, destinationPath: string): string {
const implicitTls = protocol === "ftps" && port === 990;
const scheme = implicitTls ? "ftps" : "ftp";
const bracketedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
const url = new URL(`${scheme}://${bracketedHost}`);
url.port = String(port);
url.pathname = `/${encodedPath(pathParts(destinationPath))}`;
return url.toString();
}
function createFtpUploader(config: DestinationRuntimeConfig): BlobUploader {
const protocol = optionString(config, "protocol", "sftp");
if (protocol !== "ftp" && protocol !== "ftps" && protocol !== "sftp") {
throw new Error("Destination option protocol must be ftp, ftps, or sftp");
}
const host = requiredStringOption(config, "host");
const username = requireCredential(config, "username");
const directory = optionString(config, "path");
const publicBase = requiredStringOption(config, "publicBaseUrl");
httpBase(publicBase, "publicBaseUrl");
if (protocol === "sftp") {
const port = optionNumber(config, "port", 22) ?? 22;
const keyPath = credentialString(config, "privateKey");
const password = credentialString(config, "password");
if (password && !keyPath) {
throw new DestinationUnavailableError(
"ftp",
"SFTP password injection is unsupported by the shared SSH transport; configure a private-key path or SSH agent",
);
}
if (keyPath?.includes("-----BEGIN")) {View on GitHub (pinned to 9690622007)
Solutions
- Set options.protocol to exactly one of "ftp", "ftps", or "sftp" (lowercase).
- If you meant SCP over SSH, use "sftp" — the shared SSH transport (writeRemoteFile) is used for it.
- For explicit FTPS (AUTH TLS on port 21), use "ftps" with a port other than 990; the uploader sends curl --ssl-reqd in that case.
- Remove the protocol option entirely to get the default ("sftp") if that is what you want.
Example fix
// before
{ "destination": "ftp", "options": { "protocol": "SFTP", "host": "box.example.com", "publicBaseUrl": "https://f.example.com" } }
// after
{ "destination": "ftp", "options": { "protocol": "sftp", "host": "box.example.com", "publicBaseUrl": "https://f.example.com" } } Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = new Set(['ftp', 'ftps', 'sftp']);
const protocol = config.options?.protocol ?? 'sftp';
if (!ALLOWED.has(protocol)) throw new Error(`protocol must be one of ftp, ftps, sftp (got ${protocol})`); Type guard
const isFtpProtocol = (v) => v === 'ftp' || v === 'ftps' || v === 'sftp';
Try / catch
try {
const uploader = createSelfHostedUploader('ftp', config);
} catch (err) {
if (err instanceof Error && err.message.includes('must be ftp, ftps, or sftp')) {
// fix options.protocol before retrying
} else throw err;
} Prevention
- Use a lowercase string-literal union type (e.g. type Protocol = 'ftp'|'ftps'|'sftp') for config parsing.
- Normalize case with toLowerCase() when reading user-supplied protocol values.
- Document that scp is not supported; sftp covers SSH transfers.
- Validate destination options centrally at config-load time rather than at upload time.
When it happens
Trigger: Configuring a destination with destination "ftp" and options.protocol set to e.g. "scp", "SFTP", "sftps", "ftpes", or an empty string.
Common situations: Users conflating scp with sftp; typing the protocol in uppercase because the error message shows uppercased names; inventing ftpes (explicit FTPS) which is not a supported value here; leaving protocol set from a previous template.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- the configured endpoint must use HTTP or HTTPS
- the SFTP privateKey credential must be a filesystem path, no
- ${protocol.toUpperCase()} requires options.commandBinary poi
- Replacement text is not valid UTF-8: {err}
- invalid glob `{pattern}`: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/c25a9689cdc0a33e.
Report an issue: GitHub.