diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 8ac0361f..e95942cb 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -26,6 +26,7 @@ edition = "2024" rust = { } [workspace.lints.clippy] +expect_used = "deny" unwrap_used = "deny" [profile.release] diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index fb8414ec..afabcea4 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -212,7 +212,9 @@ fn extract_heredoc_body_from_apply_patch_command(src: &str) -> anyhow::Result anyhow::Result anyhow::Result<()> { }; match event { Ok(event) => { - let event_str = - serde_json::to_string(&event).expect("JSON serialization failed"); + let event_str = match serde_json::to_string(&event) { + Ok(s) => s, + Err(e) => { + error!("Failed to serialize event: {e}"); + continue; + } + }; println!("{event_str}"); } Err(e) => { diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 6dd20aaa..f8f30391 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -28,6 +28,7 @@ use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; use crate::client_common::Summary; use crate::error::CodexErr; +use crate::error::EnvVarError; use crate::error::Result; use crate::flags::CODEX_RS_SSE_FIXTURE; use crate::flags::OPENAI_REQUEST_MAX_RETRIES; @@ -151,10 +152,10 @@ impl ModelClient { } // Assemble tool list: built-in tools + any extra tools from the prompt. - let mut tools_json: Vec = DEFAULT_TOOLS - .iter() - .map(|t| serde_json::to_value(t).expect("serialize builtin tool")) - .collect(); + let mut tools_json = Vec::with_capacity(DEFAULT_TOOLS.len() + prompt.extra_tools.len()); + for t in DEFAULT_TOOLS.iter() { + tools_json.push(serde_json::to_value(t)?); + } tools_json.extend( prompt .extra_tools @@ -191,10 +192,12 @@ impl ModelClient { loop { attempt += 1; - let api_key = self - .provider - .api_key()? - .expect("Repsones API requires an API key"); + let api_key = self.provider.api_key()?.ok_or_else(|| { + CodexErr::EnvVar(EnvVarError { + var: self.provider.env_key.clone().unwrap_or_default(), + instructions: None, + }) + })?; let res = self .client .post(&url) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index bf08c10f..82296ccb 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1654,6 +1654,7 @@ fn format_exec_output(output: &str, exit_code: i32, duration: std::time::Duratio }, }; + #[expect(clippy::expect_used)] serde_json::to_string(&payload).expect("serialize ExecOutput") } diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index 2e5b3f19..6a71a45e 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -246,27 +246,30 @@ impl Config { })? .clone(); + let resolved_cwd = { + use std::env; + + match cwd { + None => { + tracing::info!("cwd not set, using current dir"); + env::current_dir()? + } + Some(p) if p.is_absolute() => p, + Some(p) => { + // Resolve relative path against the current working directory. + tracing::info!("cwd is relative, resolving against current dir"); + let mut current = env::current_dir()?; + current.push(p); + current + } + } + }; + let config = Self { model: model.or(cfg.model).unwrap_or_else(default_model), model_provider_id, model_provider, - cwd: cwd.map_or_else( - || { - tracing::info!("cwd not set, using current dir"); - std::env::current_dir().expect("cannot determine current dir") - }, - |p| { - if p.is_absolute() { - p - } else { - // Resolve relative paths against the current working directory. - tracing::info!("cwd is relative, resolving against current dir"); - let mut cwd = std::env::current_dir().expect("cannot determine cwd"); - cwd.push(p); - cwd - } - }, - ), + cwd: resolved_cwd, approval_policy: approval_policy .or(cfg.approval_policy) .unwrap_or_else(AskForApproval::default), @@ -292,6 +295,7 @@ impl Config { /// Meant to be used exclusively for tests: `load_with_overrides()` should /// be used in all other cases. pub fn load_default_config_for_test() -> Self { + #[expect(clippy::expect_used)] Self::load_from_base_config_with_overrides( ConfigToml::default(), ConfigOverrides::default(), @@ -371,6 +375,7 @@ pub fn parse_sandbox_permission_with_base_path( #[cfg(test)] mod tests { + #![allow(clippy::expect_used, clippy::unwrap_used)] use super::*; /// Verify that the `sandbox_permissions` field on `ConfigToml` correctly diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 35ee96b8..de758c88 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -344,13 +344,30 @@ pub(crate) async fn consume_truncated_output( ctrl_c: Arc, timeout_ms: Option, ) -> Result { + // Both stdout and stderr were configured with `Stdio::piped()` + // above, therefore `take()` should normally return `Some`. If it doesn't + // we treat it as an exceptional I/O error + + let stdout_reader = child.stdout.take().ok_or_else(|| { + CodexErr::Io(io::Error::new( + io::ErrorKind::Other, + "stdout pipe was unexpectedly not available", + )) + })?; + let stderr_reader = child.stderr.take().ok_or_else(|| { + CodexErr::Io(io::Error::new( + io::ErrorKind::Other, + "stderr pipe was unexpectedly not available", + )) + })?; + let stdout_handle = tokio::spawn(read_capped( - BufReader::new(child.stdout.take().expect("stdout is not piped")), + BufReader::new(stdout_reader), MAX_STREAM_OUTPUT, MAX_STREAM_OUTPUT_LINES, )); let stderr_handle = tokio::spawn(read_capped( - BufReader::new(child.stderr.take().expect("stderr is not piped")), + BufReader::new(stderr_reader), MAX_STREAM_OUTPUT, MAX_STREAM_OUTPUT_LINES, )); diff --git a/codex-rs/core/src/exec_linux.rs b/codex-rs/core/src/exec_linux.rs index 883a46a1..22e97ea4 100644 --- a/codex-rs/core/src/exec_linux.rs +++ b/codex-rs/core/src/exec_linux.rs @@ -27,8 +27,7 @@ pub fn exec_linux( let tool_call_output = std::thread::spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() - .build() - .expect("Failed to create runtime"); + .build()?; rt.block_on(async { let ExecParams { diff --git a/codex-rs/core/src/is_safe_command.rs b/codex-rs/core/src/is_safe_command.rs index 5c688bac..5c5b0d01 100644 --- a/codex-rs/core/src/is_safe_command.rs +++ b/codex-rs/core/src/is_safe_command.rs @@ -75,6 +75,7 @@ fn is_safe_to_call_with_exec(command: &[String]) -> bool { fn try_parse_bash(bash_lc_arg: &str) -> Option { let lang = BASH.into(); let mut parser = Parser::new(); + #[expect(clippy::expect_used)] parser.set_language(&lang).expect("load bash grammar"); let old_tree: Option<&Tree> = None; diff --git a/codex-rs/core/src/landlock.rs b/codex-rs/core/src/landlock.rs index e8f5a4de..9a1d2849 100644 --- a/codex-rs/core/src/landlock.rs +++ b/codex-rs/core/src/landlock.rs @@ -140,7 +140,7 @@ fn install_network_seccomp_filter_on_current_thread() -> std::result::Result<(), #[cfg(test)] mod tests { - #![allow(clippy::unwrap_used)] + #![expect(clippy::unwrap_used, clippy::expect_used)] use super::*; use crate::exec::ExecParams; diff --git a/codex-rs/core/src/project_doc.rs b/codex-rs/core/src/project_doc.rs index d468d61d..1ba0dd70 100644 --- a/codex-rs/core/src/project_doc.rs +++ b/codex-rs/core/src/project_doc.rs @@ -134,7 +134,7 @@ async fn load_first_candidate( #[cfg(test)] mod tests { - #![allow(clippy::unwrap_used)] + #![allow(clippy::expect_used, clippy::unwrap_used)] use super::*; use crate::config::Config; diff --git a/codex-rs/core/tests/live_agent.rs b/codex-rs/core/tests/live_agent.rs index 5eb275b4..c43c5c19 100644 --- a/codex-rs/core/tests/live_agent.rs +++ b/codex-rs/core/tests/live_agent.rs @@ -1,3 +1,5 @@ +#![expect(clippy::unwrap_used, clippy::expect_used)] + //! Live integration tests that exercise the full [`Agent`] stack **against the real //! OpenAI `/v1/responses` API**. These tests complement the lightweight mock‑based //! unit tests by verifying that the agent can drive an end‑to‑end conversation, @@ -65,7 +67,6 @@ async fn spawn_codex() -> Result { #[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn live_streaming_and_prev_id_reset() { - #![allow(clippy::unwrap_used)] if !api_key_available() { eprintln!("skipping live_streaming_and_prev_id_reset – OPENAI_API_KEY not set"); return; @@ -140,7 +141,6 @@ async fn live_streaming_and_prev_id_reset() { #[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn live_shell_function_call() { - #![allow(clippy::unwrap_used)] if !api_key_available() { eprintln!("skipping live_shell_function_call – OPENAI_API_KEY not set"); return; diff --git a/codex-rs/core/tests/live_cli.rs b/codex-rs/core/tests/live_cli.rs index 5561abb8..d79e242c 100644 --- a/codex-rs/core/tests/live_cli.rs +++ b/codex-rs/core/tests/live_cli.rs @@ -1,3 +1,5 @@ +#![expect(clippy::expect_used)] + //! Optional smoke tests that hit the real OpenAI /v1/responses endpoint. They are `#[ignore]` by //! default so CI stays deterministic and free. Developers can run them locally with //! `cargo test --test live_cli -- --ignored` provided they set a valid `OPENAI_API_KEY`. diff --git a/codex-rs/execpolicy/tests/bad.rs b/codex-rs/execpolicy/tests/bad.rs index 8b6e195f..5c2999a0 100644 --- a/codex-rs/execpolicy/tests/bad.rs +++ b/codex-rs/execpolicy/tests/bad.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use codex_execpolicy::NegativeExamplePassedCheck; use codex_execpolicy::get_default_policy; diff --git a/codex-rs/execpolicy/tests/cp.rs b/codex-rs/execpolicy/tests/cp.rs index f34c7fc6..14fd2441 100644 --- a/codex-rs/execpolicy/tests/cp.rs +++ b/codex-rs/execpolicy/tests/cp.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] extern crate codex_execpolicy; use codex_execpolicy::ArgMatcher; diff --git a/codex-rs/execpolicy/tests/good.rs b/codex-rs/execpolicy/tests/good.rs index 3b7313a3..645728af 100644 --- a/codex-rs/execpolicy/tests/good.rs +++ b/codex-rs/execpolicy/tests/good.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use codex_execpolicy::PositiveExampleFailedCheck; use codex_execpolicy::get_default_policy; diff --git a/codex-rs/execpolicy/tests/head.rs b/codex-rs/execpolicy/tests/head.rs index d843ac7d..8d7a165c 100644 --- a/codex-rs/execpolicy/tests/head.rs +++ b/codex-rs/execpolicy/tests/head.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use codex_execpolicy::ArgMatcher; use codex_execpolicy::ArgType; use codex_execpolicy::Error; diff --git a/codex-rs/execpolicy/tests/literal.rs b/codex-rs/execpolicy/tests/literal.rs index d849371e..629aeb9c 100644 --- a/codex-rs/execpolicy/tests/literal.rs +++ b/codex-rs/execpolicy/tests/literal.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use codex_execpolicy::ArgType; use codex_execpolicy::Error; use codex_execpolicy::ExecCall; diff --git a/codex-rs/execpolicy/tests/ls.rs b/codex-rs/execpolicy/tests/ls.rs index 5c2e47f6..854ec3bf 100644 --- a/codex-rs/execpolicy/tests/ls.rs +++ b/codex-rs/execpolicy/tests/ls.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] extern crate codex_execpolicy; use codex_execpolicy::ArgType; diff --git a/codex-rs/execpolicy/tests/pwd.rs b/codex-rs/execpolicy/tests/pwd.rs index 0fc46f13..339908e9 100644 --- a/codex-rs/execpolicy/tests/pwd.rs +++ b/codex-rs/execpolicy/tests/pwd.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] extern crate codex_execpolicy; use std::vec; diff --git a/codex-rs/execpolicy/tests/sed.rs b/codex-rs/execpolicy/tests/sed.rs index dfd7cfd0..064e5393 100644 --- a/codex-rs/execpolicy/tests/sed.rs +++ b/codex-rs/execpolicy/tests/sed.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] extern crate codex_execpolicy; use codex_execpolicy::ArgType; diff --git a/codex-rs/mcp-server/src/codex_tool_config.rs b/codex-rs/mcp-server/src/codex_tool_config.rs index 89b19f72..78080795 100644 --- a/codex-rs/mcp-server/src/codex_tool_config.rs +++ b/codex-rs/mcp-server/src/codex_tool_config.rs @@ -117,6 +117,8 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool { }) .into_generator() .into_root_schema_for::(); + + #[expect(clippy::expect_used)] let schema_value = serde_json::to_value(&schema).expect("Codex tool schema should serialise to JSON"); @@ -186,6 +188,7 @@ mod tests { #[test] fn verify_codex_tool_json_schema() { let tool = create_tool_for_codex_tool_call_param(); + #[expect(clippy::expect_used)] let tool_json = serde_json::to_value(&tool).expect("tool serializes"); let expected_tool_json = serde_json::json!({ "name": "codex", diff --git a/codex-rs/mcp-server/src/codex_tool_runner.rs b/codex-rs/mcp-server/src/codex_tool_runner.rs index 4d2b1439..2f8a1a34 100644 --- a/codex-rs/mcp-server/src/codex_tool_runner.rs +++ b/codex-rs/mcp-server/src/codex_tool_runner.rs @@ -19,6 +19,7 @@ use tokio::sync::mpsc::Sender; /// Convert a Codex [`Event`] to an MCP notification. fn codex_event_to_notification(event: &Event) -> JSONRPCMessage { + #[expect(clippy::expect_used)] JSONRPCMessage::Notification(mcp_types::JSONRPCNotification { jsonrpc: JSONRPC_VERSION.into(), method: "codex/event".into(), diff --git a/codex-rs/mcp-types/tests/initialize.rs b/codex-rs/mcp-types/tests/initialize.rs index 69e8b3a6..27902dce 100644 --- a/codex-rs/mcp-types/tests/initialize.rs +++ b/codex-rs/mcp-types/tests/initialize.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use mcp_types::ClientCapabilities; use mcp_types::ClientRequest; use mcp_types::Implementation; diff --git a/codex-rs/mcp-types/tests/progress_notification.rs b/codex-rs/mcp-types/tests/progress_notification.rs index 396efca2..b518e4a9 100644 --- a/codex-rs/mcp-types/tests/progress_notification.rs +++ b/codex-rs/mcp-types/tests/progress_notification.rs @@ -1,3 +1,4 @@ +#![expect(clippy::expect_used)] use mcp_types::JSONRPCMessage; use mcp_types::ProgressNotificationParams; use mcp_types::ProgressToken;