can1357/oh-my-pi · error

SSH key is not a file: ${keyPath}

Error message

SSH key is not a file: ${keyPath}

What it means

After confirming the key path exists, validateKeyPermissions checks that it is a regular file. This error is thrown when the configured key path exists but is not a regular file — e.g. a directory, symlink chain target issue, socket, or device node — since ssh cannot use it as an identity file.

Source

Thrown at packages/coding-agent/src/ssh/connection-manager.ts:255

	} catch (err) {
		if (isEnoent(err)) return;
		logger.warn("Failed to delete SSH host info", { host: hostName, error: String(err) });
	}
}

async function validateKeyPermissions(keyPath?: string, platform: SshPlatform = process.platform): Promise<void> {
	if (!keyPath) return;
	let stats: fs.Stats;
	try {
		stats = await fs.promises.stat(keyPath);
	} catch (err) {
		if (isEnoent(err)) {
			throw new Error(`SSH key not found: ${keyPath}`);
		}
		throw err;
	}
	if (!stats.isFile()) {
		throw new Error(`SSH key is not a file: ${keyPath}`);
	}
	if (platform === "win32") return;
	const mode = stats.mode & 0o777;
	if ((mode & 0o077) !== 0) {
		throw new Error(`SSH key permissions must be 600 or stricter: ${keyPath}`);
	}
}

function buildCommonArgs(host: SSHConnectionTarget, options?: SSHArgsOptions): string[] {
	const args = options?.allowStdin ? [] : ["-n"];

	if (supportsSshControlMaster(options?.platform)) {
		args.push("-o", "ControlMaster=auto", "-o", `ControlPath=${CONTROL_PATH}`, "-o", "ControlPersist=3600");
	}

	args.push("-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new");

	if (host.port) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Check what is at the path: ls -ld <keyPath>; point the config at the actual private key file
  2. If a directory was created by mistake, remove it and generate/copy the key there as a file
  3. Ensure you reference the private key, not the .pub file directory or an agent socket

Example fix

// before
privateKeyPath: "/home/me/.ssh"           // a directory
// after
privateKeyPath: "/home/me/.ssh/id_ed25519" // the key file
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from "node:fs/promises";
const st = await fs.stat(target.privateKeyPath);
if (!st.isFile()) throw new Error("privateKeyPath must be a regular key file, not a directory/special file");

Type guard

function isKeyFile(stats: fs.Stats): boolean {
  return stats.isFile();
}

Try / catch

try {
  await connect(target);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("SSH key is not a file:")) {
    console.error("Point privateKeyPath at the key file itself (e.g. ~/.ssh/id_ed25519)");
  }
  throw err;
}

Prevention

When it happens

Trigger: Setting privateKeyPath to a directory (like ~/.ssh itself), to a path that is a symlink to a directory, or to a fifo/device; also when a path expanded unexpectedly (empty keyPath segment resolving to cwd).

Common situations: Config points at ~/.ssh instead of ~/.ssh/id_ed25519; a mounted path where the key appears as a directory; a broken provisioning script that created the key path as a directory.

Related errors


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