can1357/oh-my-pi · error
SSH key permissions must be 600 or stricter: ${keyPath}
Error message
SSH key permissions must be 600 or stricter: ${keyPath} What it means
On non-Windows platforms, validateKeyPermissions enforces that the private key has no group/other permission bits ((mode & 0o077) === 0), i.e. 600 or stricter. SSH itself refuses world/group-readable private keys; this library fails fast with a clear message before spawning ssh.
Source
Thrown at packages/coding-agent/src/ssh/connection-manager.ts:260
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) {
args.push("-p", String(host.port));
}
if (host.keyPath) {
args.push("-i", host.keyPath);
}View on GitHub (pinned to 9690622007)
Solutions
- chmod 600 <keyPath>
- If under WSL /mnt/c, move the key into the Linux filesystem (~/) and chmod 600, or remount with correct metadata
- Avoid storing keys in git; if you must, fix the mode after checkout
Example fix
// before $ ls -l ~/.ssh/id_ed25519 -rw-r--r-- 1 me me ... id_ed25519 // after $ chmod 600 ~/.ssh/id_ed25519
Defensive patterns
Strategy: validation
Validate before calling
import * as fs from "node:fs/promises";
if (process.platform !== "win32") {
const st = await fs.stat(keyPath);
if ((st.mode & 0o077) !== 0) {
throw new Error(`chmod 600 ${keyPath} before connecting (mode ${(st.mode & 0o777).toString(8)})`);
}
} Try / catch
try {
await connect(target);
} catch (err) {
if (err instanceof Error && err.message.includes("permissions must be 600")) {
const p = err.message.split(": ").pop()!;
await $`chmod 600 ${p}`.quiet().nothrow();
return connect(target); // retry once after fixing
}
throw err;
} Prevention
- Always chmod 600 keys after creating/copying them
- On WSL keep keys under the Linux FS (~/), not /mnt/c
- Don't commit private keys to git — checkout resets modes to 644
- Add a preflight check in deploy scripts: stat -c '%a' key
When it happens
Trigger: Connecting with a key whose file mode is e.g. 644, 664, or 755 on Linux/macOS; commonly after copying a key with scp -r, downloading it via a browser, or cloning it from git (git preserves only the executable bit, yielding 644).
Common situations: Fresh key copied from Windows/macOS to Linux, key checked into a repo and checked out with 644, WSL accessing a key under /mnt/c (drvfs mounts are 777), backup-restore that reset modes.
Related errors
- SSH control directory ${dir} ${reason}
- SSH control directory ${dir} ${isSymlink ? "is a symlink" :
- {}: {error}
- inter-device move failed: {} to {}; unable to remove target:
- Permission denied
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a8ac9aec86f9666b.
Report an issue: GitHub.