{"record":{"id":"d2b3d3f610ddb208","repo":"infiniflow/ragflow","slug":"failed-to-load-ssh-private-key-errors","errorCode":null,"errorMessage":"Failed to load SSH private key. {errors}","messagePattern":"Failed to load SSH private key\\. (.+?)","errorType":"exception","errorClass":"SandboxProviderConfigError","httpStatus":null,"severity":"critical","filePath":"agent/sandbox/providers/ssh.py","lineNumber":514,"sourceCode":"        )\n        errors: list[str] = []\n        private_key_value = self.private_key.strip()\n        passphrase = self.passphrase or None\n\n        if os.path.exists(private_key_value):\n            for key_cls in loaders:\n                try:\n                    return key_cls.from_private_key_file(private_key_value, password=passphrase)\n                except Exception as exc:\n                    errors.append(str(exc))\n        else:\n            for key_cls in loaders:\n                try:\n                    return key_cls.from_private_key(io.StringIO(private_key_value), password=passphrase)\n                except Exception as exc:\n                    errors.append(str(exc))\n\n        raise SandboxProviderConfigError(\"Failed to load SSH private key. \" + \"; \".join(error for error in errors if error))\n\n    def _create_remote_workspace(self, client: paramiko.SSHClient) -> str:\n        base_dir = self.work_dir.rstrip(\"/\") or \"/tmp\"\n        template = posixpath.join(base_dir, \"ragflow-codeexec.XXXXXX\")\n        stdout, stderr, exit_code = self._run_remote_command(\n            client,\n            f\"mkdir -p {shlex.quote(base_dir)} && mktemp -d {shlex.quote(template)}\",\n            timeout=min(self.timeout, 10),\n        )\n        if exit_code != 0:\n            raise RuntimeError(f\"Failed to create remote workspace on {self.host}: {stderr or stdout or 'unknown error'}\")\n\n        remote_work_dir = stdout.strip().splitlines()[-1] if stdout.strip() else \"\"\n        if not remote_work_dir:\n            raise RuntimeError(\"Remote workspace creation did not return a path.\")\n        return remote_work_dir\n\n    def _upload_script(","sourceCodeStart":496,"sourceCodeEnd":532,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/agent/sandbox/providers/ssh.py#L496-L532","documentation":"Raised as SandboxProviderConfigError by the private-key loader after every paramiko key class (RSAKey, Ed25519Key, ECDSAKey, etc.) failed to parse the configured private_key material. The message joins the individual per-loader errors, which distinguish wrong passphrase ('Private key file is encrypted' / decrypt errors) from wrong format ('not a valid OPENSSH or PEM key'). Both a file path and an in-memory key string are attempted depending on how the config was supplied.","triggerScenarios":"initialize() with a private_key value that is neither a readable path nor a valid PEM/OpenSSH key body; correct key but wrong passphrase; key in a format paramiko's version does not support (e.g. very new OpenSSH format on an old paramiko); key body truncated or with escaped newlines mangled by config transport.","commonSituations":"Passing a Docker/K8s secret with literal '\\n' instead of real newlines; PuTTYgen .ppk keys (unsupported by paramiko); passphrase mismatch after key rotation; ancient paramiko that cannot read openssh-key v1 format Ed25519 keys.","solutions":["Read the joined per-key errors in the message: 'encrypted' means passphrase problem, 'invalid key' means format problem","Verify the key locally: ssh-keygen -y -f <keyfile> (prompts for passphrase) or chmod 400 + ssh -i","If newlines were flattened in config, re-supply the key as a file path instead of an inline string","For unsupported formats: convert with ssh-keygen -p -m PEM -f <key> (or -t ed25519 to regenerate), or upgrade paramiko"],"exampleFix":"# before: inline key with escaped newlines from a secret\nprovider.initialize({..., \"private_key\": \"-----BEGIN...-----BEGIN OPENSSH...\\nAAA...\"})\n\n# after: pass a real file path\nprovider.initialize({..., \"private_key\": \"/secrets/id_ed25519\", \"passphrase\": \"correct-horse\"})","handlingStrategy":"validation","validationCode":"import os\nkey = config.get(\"private_key\", \"\")\nif key and not os.path.isfile(key) and \"PRIVATE KEY\" not in key:\n    raise RuntimeError(\"private_key must be a readable file path or a PEM/OpenSSH key body\")","typeGuard":null,"tryCatchPattern":"try:\n    provider.initialize(config)\nexcept SandboxProviderConfigError as e:\n    if \"Failed to load SSH private key\" in str(e):\n        # message joins per-loader errors: 'encrypted' => passphrase, else format\n        log.error(\"key load failed: %s\", e)","preventionTips":["Prefer file paths for keys over inline strings to avoid newline escaping issues in secrets","Validate keys at deploy time with ssh-keygen -y -f <key>","Keep paramiko current so modern OpenSSH key formats parse"],"tags":["ssh","authentication","private-key","paramiko","configuration"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}