Disallow expect via lints (#865)

Adds `expect()` as a denied lint. Same deal applies with `unwrap()`
where we now need to put `#[expect(...` on ones that we legit want. Took
care to enable `expect()` in test contexts.

# Tests

```
cargo fmt
cargo clippy --all-features --all-targets --no-deps -- -D warnings
cargo test
```
This commit is contained in:
jcoens-openai
2025-05-12 08:45:46 -07:00
committed by GitHub
parent a1f51bf91b
commit f3bd143867
25 changed files with 87 additions and 37 deletions

View File

@@ -26,6 +26,7 @@ edition = "2024"
rust = { }
[workspace.lints.clippy]
expect_used = "deny"
unwrap_used = "deny"
[profile.release]

View File

@@ -212,7 +212,9 @@ fn extract_heredoc_body_from_apply_patch_command(src: &str) -> anyhow::Result<St
let lang = BASH.into();
let mut parser = Parser::new();
parser.set_language(&lang).expect("load bash grammar");
parser
.set_language(&lang)
.context("failed to load bash grammar")?;
let tree = parser
.parse(src, None)
.ok_or_else(|| anyhow::anyhow!("failed to parse patch into AST"))?;
@@ -225,7 +227,7 @@ fn extract_heredoc_body_from_apply_patch_command(src: &str) -> anyhow::Result<St
if node.kind() == "heredoc_body" {
let text = node
.utf8_text(bytes)
.with_context(|| "failed to interpret heredoc body as UTF-8")?;
.context("failed to interpret heredoc body as UTF-8")?;
return Ok(text.trim_end_matches('\n').to_owned());
}

View File

@@ -81,8 +81,13 @@ pub async fn run_main(_opts: ProtoCli) -> 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) => {

View File

@@ -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<serde_json::Value> = 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)

View File

@@ -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")
}

View File

@@ -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

View File

@@ -344,13 +344,30 @@ pub(crate) async fn consume_truncated_output(
ctrl_c: Arc<Notify>,
timeout_ms: Option<u64>,
) -> Result<RawExecToolCallOutput> {
// 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,
));

View File

@@ -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 {

View File

@@ -75,6 +75,7 @@ fn is_safe_to_call_with_exec(command: &[String]) -> bool {
fn try_parse_bash(bash_lc_arg: &str) -> Option<Tree> {
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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 mockbased
//! unit tests by verifying that the agent can drive an endtoend conversation,
@@ -65,7 +67,6 @@ async fn spawn_codex() -> Result<Codex, CodexErr> {
#[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;

View File

@@ -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`.

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use codex_execpolicy::NegativeExamplePassedCheck;
use codex_execpolicy::get_default_policy;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
extern crate codex_execpolicy;
use codex_execpolicy::ArgMatcher;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use codex_execpolicy::PositiveExampleFailedCheck;
use codex_execpolicy::get_default_policy;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use codex_execpolicy::ArgMatcher;
use codex_execpolicy::ArgType;
use codex_execpolicy::Error;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use codex_execpolicy::ArgType;
use codex_execpolicy::Error;
use codex_execpolicy::ExecCall;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
extern crate codex_execpolicy;
use codex_execpolicy::ArgType;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
extern crate codex_execpolicy;
use std::vec;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
extern crate codex_execpolicy;
use codex_execpolicy::ArgType;

View File

@@ -117,6 +117,8 @@ pub(crate) fn create_tool_for_codex_tool_call_param() -> Tool {
})
.into_generator()
.into_root_schema_for::<CodexToolCallParam>();
#[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",

View File

@@ -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(),

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use mcp_types::ClientCapabilities;
use mcp_types::ClientRequest;
use mcp_types::Implementation;

View File

@@ -1,3 +1,4 @@
#![expect(clippy::expect_used)]
use mcp_types::JSONRPCMessage;
use mcp_types::ProgressNotificationParams;
use mcp_types::ProgressToken;