can1357/oh-my-pi · error
SSH control directory ${dir} ${reason}
Error message
SSH control directory ${dir} ${reason} What it means
The SSH connection manager creates a per-host ControlMaster socket directory (e.g. under ~/.ssh/omp-control). Before using it, assertOwnerPrivateDir verifies the existing directory is actually a directory, not a symlink, owned by the current UID, and has no group/other permissions. This error is thrown when the directory exists but fails that ownership/permission guard, because a shared or symlinked control dir would let other users hijack SSH master sockets.
Source
Thrown at packages/coding-agent/src/ssh/connection-manager.ts:222
}
try {
let st = fs.fstatSync(fd);
// Normalize perms on the pinned inode only when it is ours; never fchmod a
// directory another user owns.
if ((uid === undefined || st.uid === uid) && (st.mode & 0o777) !== 0o700) {
try {
fs.fchmodSync(fd, 0o700);
st = fs.fstatSync(fd);
} catch (err) {
logger.debug("SSH control dir chmod failed", { path: dir, error: String(err) });
}
}
const reason = controlDirGuardError(
{ isSymlink: false, isDir: st.isDirectory(), uid: st.uid, mode: st.mode },
uid,
);
if (reason) {
throw new Error(`SSH control directory ${dir} ${reason}`);
}
} finally {
fs.closeSync(fd);
}
}
function getHostInfoPath(name: string): string {
return path.join(HOST_INFO_DIR, `${sanitizeHostName(name)}.json`);
}
async function deleteHostInfoFromDisk(hostName: string): Promise<void> {
const path = getHostInfoPath(hostName);
try {
await fs.promises.unlink(path);
} catch (err) {
if (isEnoent(err)) return;
logger.warn("Failed to delete SSH host info", { host: hostName, error: String(err) });
}View on GitHub (pinned to 9690622007)
Solutions
- Fix ownership: chown -R $(id -u):$(id -g) <control-dir> (typically ~/.ssh/omp-control)
- Fix permissions: chmod 700 <control-dir> and chmod 700 ~/.ssh
- Remove the directory if untrusted: rm -rf <control-dir> and let the tool recreate it
- If it is a symlink, delete it and recreate as a real directory
- Avoid running the CLI as root/sudo
Example fix
// before $ ls -la ~/.ssh/omp-control drwxr-xr-x 2 root root ... omp-control // after $ sudo chown -R $USER ~/.ssh/omp-control $ chmod 700 ~/.ssh/omp-control
Defensive patterns
Strategy: validation
Validate before calling
import * as fs from "node:fs";
const dir = `${process.env.HOME}/.ssh/omp-control`;
try {
const st = fs.lstatSync(dir);
if (st.isSymbolicLink() || !st.isDirectory()) throw new Error("replace control dir");
if (st.uid !== process.getuid?.()) throw new Error("wrong owner — chown it");
if ((st.mode & 0o077) !== 0) throw new Error("too permissive — chmod 700");
} catch (e) {
if (e.code !== "ENOENT") console.warn("fix control dir before connecting:", e.message);
} Type guard
function isSafeControlDir(st: fs.Stats, uid: number): boolean {
return st.isDirectory() && st.uid === uid && (st.mode & 0o077) === 0;
} Try / catch
try {
await connect(target);
} catch (err) {
if (err instanceof Error && err.message.includes("SSH control directory")) {
// repair: rm -rf the dir / chown / chmod 700, then retry once
}
throw err;
} Prevention
- Never run the CLI with sudo; if you did, chown the control dir back
- Keep ~/.ssh at 700
- Never symlink the control directory to shared storage
- Investigate 'wrong owner' errors immediately — they can indicate tampering
When it happens
Trigger: Calling any SSH operation (connect, runSsh, ssh:// file tools) when ~/.ssh/omp-control (or the computed control dir) already exists with wrong uid or group/other-accessible mode, or when the path was replaced by a symlink so the guard reports a reason.
Common situations: Running the CLI once with sudo (directory created as root), copying a dotfiles setup where ~/.ssh is world-readable, restoring a home directory from a backup that reset ownership, or someone symlinking the control dir to /tmp.
Related errors
- SSH key permissions must be 600 or stricter: ${keyPath}
- 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/1619332a53b4641f.
Report an issue: GitHub.