can1357/oh-my-pi · error · DestinationUnavailableError

SFTP password injection is unsupported by the shared SSH tra

Error message

SFTP password injection is unsupported by the shared SSH transport; configure a private-key path or SSH agent

What it means

A DestinationUnavailableError thrown when an sftp destination defines a password credential but no privateKey path. The ftp uploader routes SFTP through the shared SSH transport (writeRemoteFile), which only supports key files or SSH agent auth; interactive password injection is not implemented, so the destination is declared unavailable at creation time.

Source

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

}

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")) {
			throw new DestinationUnavailableError(
				"ftp",
				"the SFTP privateKey credential must be a filesystem path, not key contents",
			);
		}
		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),

View on GitHub (pinned to 9690622007)

Solutions

  1. Set credentials.privateKey to the filesystem path of an SSH private key (e.g. ~/.ssh/id_ed25519).
  2. Or rely on SSH agent auth: still set a privateKey path if your agent holds the key, or use a key loaded in the agent without a password credential.
  3. If you only have password access to the server, switch options.protocol to "ftp" or "ftps" (curl path supports user:password).
  4. Add the key to ssh-agent (ssh-add) if you want agent-based auth alongside the configured key path.

Example fix

// before
{ "destination": "ftp", "options": { "protocol": "sftp", "host": "box.example.com" }, "credentials": { "username": "u", "password": "hunter2" } }
// after
{ "destination": "ftp", "options": { "protocol": "sftp", "host": "box.example.com" }, "credentials": { "username": "u", "privateKey": "/home/me/.ssh/id_ed25519" } }
Defensive patterns

Strategy: validation

Validate before calling

if (dest.protocol === 'sftp' && dest.credentials?.password && !dest.credentials?.privateKey) {
  throw new Error('sftp destinations need credentials.privateKey (path) or SSH agent; password-only is unsupported');
}

Try / catch

try {
  const uploader = createSelfHostedUploader('ftp', config);
} catch (err) {
  if (err?.name === 'DestinationUnavailableError' && /password injection is unsupported/.test(err.message)) {
    // reconfigure with a privateKey path or switch protocol to ftp/ftps
  } else throw err;
}

Prevention

When it happens

Trigger: options.protocol = "sftp" with credentials.password set and credentials.privateKey absent (or falsy), when createFtpUploader builds the uploader for destination "ftp".

Common situations: Reusing an FTP (curl-based) config's password credential on an SFTP destination; assuming password auth works because plain ftp/ftps supports it; missing that privateKey must be set even when ssh-agent is available (the check only requires a key path to be configured).

Related errors


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