{"record":{"id":"1619332a53b4641f","repo":"can1357/oh-my-pi","slug":"ssh-control-directory-dir-reason","errorCode":null,"errorMessage":"SSH control directory ${dir} ${reason}","messagePattern":"SSH control directory (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/ssh/connection-manager.ts","lineNumber":222,"sourceCode":"\t}\n\ttry {\n\t\tlet st = fs.fstatSync(fd);\n\t\t// Normalize perms on the pinned inode only when it is ours; never fchmod a\n\t\t// directory another user owns.\n\t\tif ((uid === undefined || st.uid === uid) && (st.mode & 0o777) !== 0o700) {\n\t\t\ttry {\n\t\t\t\tfs.fchmodSync(fd, 0o700);\n\t\t\t\tst = fs.fstatSync(fd);\n\t\t\t} catch (err) {\n\t\t\t\tlogger.debug(\"SSH control dir chmod failed\", { path: dir, error: String(err) });\n\t\t\t}\n\t\t}\n\t\tconst reason = controlDirGuardError(\n\t\t\t{ isSymlink: false, isDir: st.isDirectory(), uid: st.uid, mode: st.mode },\n\t\t\tuid,\n\t\t);\n\t\tif (reason) {\n\t\t\tthrow new Error(`SSH control directory ${dir} ${reason}`);\n\t\t}\n\t} finally {\n\t\tfs.closeSync(fd);\n\t}\n}\n\nfunction getHostInfoPath(name: string): string {\n\treturn path.join(HOST_INFO_DIR, `${sanitizeHostName(name)}.json`);\n}\n\nasync function deleteHostInfoFromDisk(hostName: string): Promise<void> {\n\tconst path = getHostInfoPath(hostName);\n\ttry {\n\t\tawait fs.promises.unlink(path);\n\t} catch (err) {\n\t\tif (isEnoent(err)) return;\n\t\tlogger.warn(\"Failed to delete SSH host info\", { host: hostName, error: String(err) });\n\t}","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/ssh/connection-manager.ts#L204-L240","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n$ ls -la ~/.ssh/omp-control\ndrwxr-xr-x  2 root root  ...  omp-control\n// after\n$ sudo chown -R $USER ~/.ssh/omp-control\n$ chmod 700 ~/.ssh/omp-control","handlingStrategy":"validation","validationCode":"import * as fs from \"node:fs\";\nconst dir = `${process.env.HOME}/.ssh/omp-control`;\ntry {\n  const st = fs.lstatSync(dir);\n  if (st.isSymbolicLink() || !st.isDirectory()) throw new Error(\"replace control dir\");\n  if (st.uid !== process.getuid?.()) throw new Error(\"wrong owner — chown it\");\n  if ((st.mode & 0o077) !== 0) throw new Error(\"too permissive — chmod 700\");\n} catch (e) {\n  if (e.code !== \"ENOENT\") console.warn(\"fix control dir before connecting:\", e.message);\n}","typeGuard":"function isSafeControlDir(st: fs.Stats, uid: number): boolean {\n  return st.isDirectory() && st.uid === uid && (st.mode & 0o077) === 0;\n}","tryCatchPattern":"try {\n  await connect(target);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"SSH control directory\")) {\n    // repair: rm -rf the dir / chown / chmod 700, then retry once\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["ssh","permissions","security","filesystem"],"backgroundTag":"ssh-control-dir-permission-denied","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}