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
- Check what is at the path: ls -ld <keyPath>; point the config at the actual private key file
- If a directory was created by mistake, remove it and generate/copy the key there as a file
- 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
- Reference the private key file, never ~/.ssh or a directory
- Check provisioning scripts don't mkdir the key path
- Spot-check config paths with ls -ld when setup changes
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
- SSH key not found: ${keyPath}
- Unable to read OMP_AUTH_BROKER_ACCOUNT_POOL_FILE at ${filePa
- Invalid QwenCloud Cookie header. Copy the complete Cookie re
- CoreWeave Serverless Inference requires OpenAI-Project. Set
- imageUrls exposure "ssh" requires imageUrls.publicBaseUrl
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/2cdb5a7e2eb25564.
Report an issue: GitHub.