{"record":{"id":"269aecf2855fe4e4","repo":"BigPizzaV3/CodexPlusPlus","slug":"codex-cli-executable","errorCode":null,"errorMessage":"Codex CLI 路径指向的是目录而不是可执行文件：{executable}\n请去掉结尾的路径分隔符，或补上具体的可执行文件名。","messagePattern":"Codex CLI 路径指向的是目录而不是可执行文件：(.+?)\n请去掉结尾的路径分隔符，或补上具体的可执行文件名。","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/connect/app_server.rs","lineNumber":77,"sourceCode":"///\n/// 裸命令名（codex）交给 PATH 解析，这里只校验看起来像路径的输入。\nfn validate_codex_executable(executable: &str) -> anyhow::Result<()> {\n    let looks_like_path = executable.contains('/')\n        || executable.contains('\\\\')\n        || Path::new(executable).is_absolute();\n    if !looks_like_path {\n        return Ok(());\n    }\n    let path = Path::new(executable);\n    if !path.exists() {\n        bail!(\n            \"Codex CLI 路径不存在：{executable}\\n请填 Codex CLI 可执行文件的完整路径。\\\n             macOS 桌面版通常在 /Applications/ChatGPT.app/Contents/Resources/codex；\\\n             npm 全局安装可用 `which codex` / `where codex` 查看。\"\n        );\n    }\n    if path.is_dir() {\n        bail!(\n            \"Codex CLI 路径指向的是目录而不是可执行文件：{executable}\\n\\\n             请去掉结尾的路径分隔符，或补上具体的可执行文件名。\"\n        );\n    }\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        if let Ok(metadata) = std::fs::metadata(path)\n            && metadata.permissions().mode() & 0o111 == 0\n        {\n            bail!(\"Codex CLI 没有执行权限：{executable}\");\n        }\n    }\n    Ok(())\n}\n\nimpl CodexAppServer {\n    pub async fn start(config: AppServerConfig) -> anyhow::Result<Self> {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/connect/app_server.rs#L59-L95","documentation":"validate_codex_executable detects that the configured Codex CLI path exists but is a directory rather than a file, and throws this error before spawn. This commonly happens when a trailing slash or a path to an install directory is entered instead of the full path to the executable.","triggerScenarios":"CodexAppServer::start with executable set to a directory path (e.g. \"/usr/local/bin/\" or \"/opt/codex/\") — the path.exists() check passes but path.is_dir() triggers the bail.","commonSituations":"Copy-pasting a folder path from a file manager with trailing separator, entering the npm install directory instead of the bin symlink inside it, assuming the app bundle root is the binary.","solutions":["Remove the trailing path separator and append the executable name (e.g. /opt/codex/bin/codex)","Use `which codex` / `where codex` to get the exact executable path","On macOS desktop installs use the full bundle path /Applications/ChatGPT.app/Contents/Resources/codex"],"exampleFix":"// before\nAppServerConfig { executable: \"/Applications/ChatGPT.app/Contents/Resources/\".into(), .. }\n// after\nAppServerConfig { executable: \"/Applications/ChatGPT.app/Contents/Resources/codex\".into(), .. }","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn is_regular_file(executable: &str) -> bool {\n    let p = Path::new(executable);\n    p.is_file()\n}","typeGuard":null,"tryCatchPattern":"match CodexAppServer::start(config).await {\n    Err(e) if e.to_string().contains(\"目录而不是可执行文件\") => {\n        eprintln!(\"Executable points at a directory; append the binary name\");\n        // e.g. join(\"codex\") onto the directory and retry\n    }\n    r => r?,\n}","preventionTips":["Always point at a file, never a directory; strip trailing separators","When copying paths from file managers, remove the trailing slash","If pointing at an install root, append bin/<binary> or the bundle's codex path"],"tags":["filesystem","configuration","validation","path"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}