{"record":{"id":"e9ac8adb4a0755b7","repo":"t8y2/dbx","slug":"failed-to-persist-host-key-for-host-port-to-k","errorCode":null,"errorMessage":"Failed to persist host key for {host}:{port} to {known_hosts_path} ({e}). The host is trusted for this session only.","messagePattern":"Failed to persist host key for (.+?):(.+?) to (.+?) \\((.+?)\\)\\. The host is trusted for this session only\\.","errorType":"exception","errorClass":"io::Error (PermissionDenied)","httpStatus":null,"severity":"warning","filePath":"crates/dbx-core/src/db/ssh_host_key.rs","lineNumber":98,"sourceCode":"            Ok(true) => return Ok(HostKeyState::Trusted),\n            Err(russh::keys::Error::KeyChanged { line }) => {\n                return Err(host_key_changed_error(host, port, line, &self.known_hosts_path.display().to_string()));\n            }\n            // Unknown (or an unreadable dbx store): report as a candidate for TOFU.\n            _ => {}\n        }\n\n        Ok(HostKeyState::Unknown)\n    }\n\n    /// Records a host key into the dbx store (TOFU persistence). Called by the\n    /// caller only after the user explicitly accepts the key. A write failure\n    /// is reported (so the caller knows persistence did not happen) but does\n    /// not by itself abort the session — the host may simply be trusted for\n    /// this session only.\n    pub fn learn(&self, host: &str, port: u16, key: &PublicKey) -> Result<(), io::Error> {\n        learn_known_hosts_path(host, port, key, &self.known_hosts_path).map_err(|e| {\n            io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                format!(\n                    \"Failed to persist host key for {host}:{port} to {} ({e}). \\\n                     The host is trusted for this session only.\",\n                    self.known_hosts_path.display()\n                ),\n            )\n        })\n    }\n}\n\nfn host_key_changed_error(host: &str, port: u16, line: usize, store: &str) -> io::Error {\n    io::Error::other(format!(\n        \"Host key for {host}:{port} changed (recorded at {store}, line {line}). \\\n         This may indicate a man-in-the-middle attack. Remove the old entry and reconnect only if you expect this change.\"\n    ))\n}\n","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/crates/dbx-core/src/db/ssh_host_key.rs#L80-L116","documentation":"KnownHostsStore::learn persists an accepted SSH host key to the known_hosts file. If the write fails, the io::Error is replaced with an error of kind PermissionDenied carrying 'Failed to persist host key for {host}:{port} to {path} ({e}). The host is trusted for this session only.' Persistence failure does not abort the connection — the key is trusted in-memory for the session, but TOFU trust will not survive restarts.","triggerScenarios":"Calling learn after a user accepts an unknown host key when the known_hosts file cannot be written: read-only filesystem, missing parent directory, insufficient file permissions, or disk full.","commonSituations":"Running the app in a container/home directory mounted read-only, ~/.ssh/known_hosts owned by root after a sudo run, read-only CI workspace, or a known_hosts_path pointing to a nonexistent directory.","solutions":["Check and fix permissions on the known_hosts file and its parent directory so the running user can write.","Ensure the directory for known_hosts_path exists (create it before connecting).","If the filesystem is read-only (containers/CI), point known_hosts_path at a writable volume.","Accept that the host is trusted for the session only, and expect the TOFU prompt again next session."],"exampleFix":"// before\nlet store = KnownHostsStore::new(\"/readonly/.ssh/known_hosts\");\n// after\nlet store = KnownHostsStore::new(\"/tmp/writable/.ssh/known_hosts\"); // or ensure dir exists & writable","handlingStrategy":"try-catch","validationCode":"// Rust\nlet path = store.known_hosts_path();\nif let Some(dir) = path.parent() {\n    std::fs::create_dir_all(dir)?;\n}\nlet probe = std::fs::OpenOptions::new().append(true).create(true).open(&path);\nif let Err(e) = probe {\n    eprintln!(\"known_hosts not writable: {e}\");\n}","typeGuard":null,"tryCatchPattern":"// Rust\nif let Err(e) = store.learn(host, port, &key) {\n    if e.kind() == std::io::ErrorKind::PermissionDenied {\n        log::warn!(\"{e}; continuing with session-only trust\");\n    }\n}","preventionTips":["Provision a writable known_hosts path (and parent directory) at startup, not at first connect.","Avoid running as root/sudo in a way that leaves ~/.ssh files root-owned.","In containers/CI, mount a writable volume for SSH state."],"tags":["ssh","host-key","tofu","filesystem"],"backgroundTag":"known-hosts-write-failed","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}