diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 64d60bee3..dc7ec5554 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -8126,6 +8126,10 @@ impl CodexMessageProcessor { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "listener subscription must be serialized against pending thread unloads" + )] async fn ensure_conversation_listener_task( listener_task_context: ListenerTaskContext, conversation_id: ThreadId, @@ -8933,6 +8937,10 @@ async fn handle_thread_listener_command( } #[allow(clippy::too_many_arguments)] +#[expect( + clippy::await_holding_invalid_type, + reason = "running-thread resume subscription must be serialized against pending unloads" +)] async fn handle_pending_thread_resume_request( conversation_id: ThreadId, conversation: &Arc, diff --git a/codex-rs/app-server/src/message_processor/tracing_tests.rs b/codex-rs/app-server/src/message_processor/tracing_tests.rs index adf583d8f..83f8bc98d 100644 --- a/codex-rs/app-server/src/message_processor/tracing_tests.rs +++ b/codex-rs/app-server/src/message_processor/tracing_tests.rs @@ -505,6 +505,10 @@ where } #[tokio::test(flavor = "current_thread")] +#[expect( + clippy::await_holding_invalid_type, + reason = "test serializes access to global tracing state for its full duration" +)] async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> Result<()> { let _guard = tracing_test_guard().lock().await; let mut harness = TracingHarness::new().await?; @@ -584,6 +588,10 @@ async fn thread_start_jsonrpc_span_exports_server_span_and_parents_children() -> } #[tokio::test(flavor = "current_thread")] +#[expect( + clippy::await_holding_invalid_type, + reason = "test serializes access to global tracing state for its full duration" +)] async fn turn_start_jsonrpc_span_parents_core_turn_spans() -> Result<()> { let _guard = tracing_test_guard().lock().await; let mut harness = TracingHarness::new().await?; diff --git a/codex-rs/app-server/src/transport/remote_control/websocket.rs b/codex-rs/app-server/src/transport/remote_control/websocket.rs index ac91882f5..dee177d30 100644 --- a/codex-rs/app-server/src/transport/remote_control/websocket.rs +++ b/codex-rs/app-server/src/transport/remote_control/websocket.rs @@ -205,6 +205,10 @@ impl RemoteControlWebsocket { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "remote-control client shutdown must serialize tracker state" + )] pub(crate) async fn run( mut self, app_server_client_name_rx: Option>, @@ -416,6 +420,10 @@ impl RemoteControlWebsocket { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "remote-control server event receiver is shared across reconnects" + )] async fn run_server_writer_inner( state: Arc>, server_event_rx: Arc>>, @@ -557,6 +565,10 @@ impl RemoteControlWebsocket { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "remote-control client tracking must stay serialized while processing inbound events" + )] async fn run_websocket_reader_inner( client_tracker: Arc>, state: Arc>, diff --git a/codex-rs/clippy.toml b/codex-rs/clippy.toml index a2aae6591..2feed8a48 100644 --- a/codex-rs/clippy.toml +++ b/codex-rs/clippy.toml @@ -1,5 +1,10 @@ allow-expect-in-tests = true allow-unwrap-in-tests = true +await-holding-invalid-types = [ + "tokio::sync::MutexGuard", + "tokio::sync::RwLockReadGuard", + "tokio::sync::RwLockWriteGuard", +] disallowed-methods = [ { path = "ratatui::style::Color::Rgb", reason = "Use ANSI colors, which work better in various terminal themes." }, { path = "ratatui::style::Color::Indexed", reason = "Use ANSI colors, which work better in various terminal themes." }, diff --git a/codex-rs/codex-api/src/endpoint/responses_websocket.rs b/codex-rs/codex-api/src/endpoint/responses_websocket.rs index bc72aee41..aea9b917f 100644 --- a/codex-rs/codex-api/src/endpoint/responses_websocket.rs +++ b/codex-rs/codex-api/src/endpoint/responses_websocket.rs @@ -229,6 +229,10 @@ impl ResponsesWebsocketConnection { let current_span = Span::current(); tokio::spawn( + #[expect( + clippy::await_holding_invalid_type, + reason = "the guard serializes exclusive use of the websocket stream for the lifetime of the response stream" + )] async move { if let Some(model) = server_model { let _ = tx_event.send(Ok(ResponseEvent::ServerModel(model))).await; diff --git a/codex-rs/connectors/src/lib.rs b/codex-rs/connectors/src/lib.rs index e13270a78..e6260d0e1 100644 --- a/codex-rs/connectors/src/lib.rs +++ b/codex-rs/connectors/src/lib.rs @@ -446,6 +446,10 @@ mod tests { } #[tokio::test] + #[expect( + clippy::await_holding_invalid_type, + reason = "test serializes access to the shared connector cache for its full duration" + )] async fn list_all_connectors_uses_shared_cache() -> anyhow::Result<()> { let _cache_guard = ALL_CONNECTORS_CACHE_TEST_LOCK.lock().await; @@ -486,6 +490,10 @@ mod tests { } #[tokio::test] + #[expect( + clippy::await_holding_invalid_type, + reason = "test serializes access to the shared connector cache for its full duration" + )] async fn list_all_connectors_merges_and_normalizes_directory_apps() -> anyhow::Result<()> { let _cache_guard = ALL_CONNECTORS_CACHE_TEST_LOCK.lock().await; diff --git a/codex-rs/core/src/guardian/review_session.rs b/codex-rs/core/src/guardian/review_session.rs index 3a8e47574..5754d090b 100644 --- a/codex-rs/core/src/guardian/review_session.rs +++ b/codex-rs/core/src/guardian/review_session.rs @@ -265,6 +265,10 @@ impl GuardianReviewSessionManager { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "review session selection and trunk spawning must stay serialized" + )] pub(crate) async fn run_review( &self, params: GuardianReviewSessionParams, diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 32c7cf879..c5d4068d2 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -494,6 +494,10 @@ async fn execute_mcp_tool_call( ) } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP sandbox metadata reads through the session-owned manager guard" +)] async fn augment_mcp_tool_request_meta_with_sandbox_state( sess: &Session, turn_context: &TurnContext, @@ -1067,6 +1071,10 @@ fn mcp_tool_approval_callsite_mode( } } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP approval metadata reads through the session-owned manager guard" +)] pub(crate) async fn lookup_mcp_tool_metadata( sess: &Session, turn_context: &TurnContext, @@ -1149,6 +1157,10 @@ fn get_mcp_app_resource_uri( }) } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP app metadata reads through the session-owned manager guard" +)] async fn lookup_mcp_app_usage_metadata( sess: &Session, server: &str, diff --git a/codex-rs/core/src/session/handlers.rs b/codex-rs/core/src/session/handlers.rs index 36a449154..71efd2332 100644 --- a/codex-rs/core/src/session/handlers.rs +++ b/codex-rs/core/src/session/handlers.rs @@ -468,6 +468,10 @@ pub async fn reload_user_config(sess: &Arc) { sess.reload_user_config_layer().await; } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP tool listing reads through the session-owned manager guard" +)] pub async fn list_mcp_tools(sess: &Session, config: &Arc, sub_id: String) { let mcp_connection_manager = sess.services.mcp_connection_manager.read().await; let auth = sess.services.auth_manager.auth().await; diff --git a/codex-rs/core/src/session/mcp.rs b/codex-rs/core/src/session/mcp.rs index d15ca2cae..be7504d9e 100644 --- a/codex-rs/core/src/session/mcp.rs +++ b/codex-rs/core/src/session/mcp.rs @@ -1,6 +1,10 @@ use super::*; impl Session { + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn request_mcp_server_elicitation( &self, turn_context: &TurnContext, @@ -80,6 +84,10 @@ impl Session { rx_response.await.ok() } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and manager fallback must stay serialized" + )] pub async fn resolve_elicitation( &self, server_name: String, @@ -111,6 +119,10 @@ impl Session { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP resource calls are serialized through the session-owned manager guard" + )] pub async fn list_resources( &self, server: &str, @@ -124,6 +136,10 @@ impl Session { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP resource calls are serialized through the session-owned manager guard" + )] pub async fn list_resource_templates( &self, server: &str, @@ -137,6 +153,10 @@ impl Session { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP resource calls are serialized through the session-owned manager guard" + )] pub async fn read_resource( &self, server: &str, @@ -150,6 +170,10 @@ impl Session { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP tool calls are serialized through the session-owned manager guard" + )] pub async fn call_tool( &self, server: &str, @@ -165,6 +189,10 @@ impl Session { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP tool metadata reads through the session-owned manager guard" + )] pub(crate) async fn resolve_mcp_tool_info(&self, tool_name: &ToolName) -> Option { self.services .mcp_connection_manager diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 3ea826c49..46d0c248b 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -1786,6 +1786,10 @@ impl Session { /// be used to derive the available decisions via /// [ExecApprovalRequestEvent::default_available_decisions]. #[allow(clippy::too_many_arguments)] + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn request_command_approval( &self, turn_context: &TurnContext, @@ -1857,6 +1861,10 @@ impl Session { rx_approve.await.unwrap_or(ReviewDecision::Abort) } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn request_patch_approval( &self, turn_context: &TurnContext, @@ -1893,6 +1901,10 @@ impl Session { rx_approve } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn request_permissions( self: &Arc, turn_context: &Arc, @@ -2023,6 +2035,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn request_user_input( &self, turn_context: &TurnContext, @@ -2055,6 +2071,10 @@ impl Session { rx_response.await.ok() } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn notify_user_input_response( &self, sub_id: &str, @@ -2080,6 +2100,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn notify_request_permissions_response( &self, call_id: &str, @@ -2136,6 +2160,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn reads must stay consistent with the matching turn state" + )] pub(crate) async fn granted_turn_permissions(&self) -> Option { let active = self.active_turn.lock().await; let active = active.as_ref()?; @@ -2148,6 +2176,10 @@ impl Session { state.granted_permissions() } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn notify_dynamic_tool_response(&self, call_id: &str, response: DynamicToolResponse) { let entry = { let mut active = self.active_turn.lock().await; @@ -2169,6 +2201,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn notify_approval(&self, approval_id: &str, decision: ReviewDecision) { let entry = { let mut active = self.active_turn.lock().await; @@ -2329,6 +2365,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "MCP app context rendering reads through the session-owned manager guard" + )] pub(crate) async fn build_initial_context( &self, turn_context: &TurnContext, @@ -2791,6 +2831,10 @@ impl Session { /// Inject additional user input into the currently active turn. /// /// Returns the active turn id when accepted. + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn steer_input( &self, input: Vec, @@ -2850,6 +2894,10 @@ impl Session { } /// Returns the input if there was no task running to inject into. + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn inject_response_items( &self, input: Vec, @@ -2923,6 +2971,10 @@ impl Session { self.mailbox_rx.lock().await.has_pending_trigger_turn() } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn prepend_pending_input(&self, input: Vec) -> Result<(), ()> { let mut active = self.active_turn.lock().await; match active.as_mut() { @@ -2935,6 +2987,10 @@ impl Session { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state updates must remain atomic" + )] pub async fn get_pending_input(&self) -> Vec { let (pending_input, accepts_mailbox_delivery) = { let mut active = self.active_turn.lock().await; @@ -2990,6 +3046,10 @@ impl Session { !self.idle_pending_input.lock().await.is_empty() } + #[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and turn state reads must remain atomic" + )] pub async fn has_pending_input(&self) -> bool { let (has_turn_pending_input, accepts_mailbox_delivery) = { let active = self.active_turn.lock().await; diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index a2080bbea..16e86e3ae 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -209,6 +209,10 @@ pub(crate) struct AppServerClientMetadata { impl Session { #[instrument(name = "session_init", level = "info", skip_all)] #[allow(clippy::too_many_arguments)] + #[expect( + clippy::await_holding_invalid_type, + reason = "session initialization must serialize access through session-owned manager guards" + )] pub(crate) async fn new( mut session_configuration: SessionConfiguration, config: Arc, diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 18cefba93..97b6eba8b 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -6359,6 +6359,10 @@ async fn abort_review_task_emits_exited_then_aborted_and_records_history() { } #[tokio::test] +#[expect( + clippy::await_holding_invalid_type, + reason = "test builds a router from session-owned MCP manager state" +)] async fn fatal_tool_error_stops_turn_and_reports_error() { let (session, turn_context, _rx) = make_session_and_context_with_rx().await; let tools = { diff --git a/codex-rs/core/src/session/tests/guardian_tests.rs b/codex-rs/core/src/session/tests/guardian_tests.rs index 108d09575..9b1172d57 100644 --- a/codex-rs/core/src/session/tests/guardian_tests.rs +++ b/codex-rs/core/src/session/tests/guardian_tests.rs @@ -489,6 +489,10 @@ async fn process_compacted_history_preserves_separate_guardian_developer_message #[tokio::test] #[cfg(unix)] +#[expect( + clippy::await_holding_invalid_type, + reason = "test mutates active turn state directly to seed granted permissions" +)] async fn shell_handler_allows_sticky_turn_permissions_without_inline_request_permissions_feature() { let (mut session, turn_context_raw) = make_session_and_context().await; session diff --git a/codex-rs/core/src/session/turn.rs b/codex-rs/core/src/session/turn.rs index f61380224..4f6b062f9 100644 --- a/codex-rs/core/src/session/turn.rs +++ b/codex-rs/core/src/session/turn.rs @@ -128,6 +128,10 @@ use tracing::warn; /// - If the model sends only an assistant message, we record it in the /// conversation history and consider the turn complete. /// +#[expect( + clippy::await_holding_invalid_type, + reason = "turn execution must keep active-turn state transitions atomic" +)] pub(crate) async fn run_turn( sess: Arc, turn_context: Arc, @@ -1149,6 +1153,10 @@ async fn run_sampling_request( } } +#[expect( + clippy::await_holding_invalid_type, + reason = "tool router construction reads through the session-owned manager guard" +)] pub(crate) async fn built_tools( sess: &Session, turn_context: &TurnContext, diff --git a/codex-rs/core/src/tools/code_mode/mod.rs b/codex-rs/core/src/tools/code_mode/mod.rs index 504983639..575d3f9a8 100644 --- a/codex-rs/core/src/tools/code_mode/mod.rs +++ b/codex-rs/core/src/tools/code_mode/mod.rs @@ -259,6 +259,10 @@ pub(super) async fn build_enabled_tools( collect_code_mode_tool_definitions(&specs) } +#[expect( + clippy::await_holding_invalid_type, + reason = "nested tool router construction reads through the session-owned manager guard" +)] async fn build_nested_router(exec: &ExecContext) -> ToolRouter { let nested_tools_config = exec.turn.tools_config.for_code_mode_nested_tools(); let listed_mcp_tools = exec diff --git a/codex-rs/core/src/tools/handlers/dynamic.rs b/codex-rs/core/src/tools/handlers/dynamic.rs index 140d586be..c989d70f1 100644 --- a/codex-rs/core/src/tools/handlers/dynamic.rs +++ b/codex-rs/core/src/tools/handlers/dynamic.rs @@ -71,6 +71,10 @@ impl ToolHandler for DynamicToolHandler { } } +#[expect( + clippy::await_holding_invalid_type, + reason = "active turn checks and dynamic tool response registration must remain atomic" +)] async fn request_dynamic_tool( session: &Session, turn_context: &TurnContext, diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index 67f44cb69..fa4a06674 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -240,6 +240,10 @@ impl ToolHandler for McpResourceHandler { } } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP resource listing reads through the session-owned manager guard" +)] async fn handle_list_resources( session: Arc, turn: Arc, @@ -344,6 +348,10 @@ async fn handle_list_resources( } } +#[expect( + clippy::await_holding_invalid_type, + reason = "MCP resource template listing reads through the session-owned manager guard" +)] async fn handle_list_resource_templates( session: Arc, turn: Arc, diff --git a/codex-rs/core/src/tools/handlers/tool_suggest.rs b/codex-rs/core/src/tools/handlers/tool_suggest.rs index cc433601b..5c51c90d6 100644 --- a/codex-rs/core/src/tools/handlers/tool_suggest.rs +++ b/codex-rs/core/src/tools/handlers/tool_suggest.rs @@ -34,6 +34,10 @@ impl ToolHandler for ToolSuggestHandler { ToolKind::Function } + #[expect( + clippy::await_holding_invalid_type, + reason = "tool suggestion discovery reads through the session-owned manager guard" + )] async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { payload, @@ -193,6 +197,10 @@ async fn verify_tool_suggestion_completed( } } +#[expect( + clippy::await_holding_invalid_type, + reason = "connector cache refresh reads through the session-owned manager guard" +)] async fn refresh_missing_suggested_connectors( session: &crate::session::session::Session, turn: &crate::session::turn_context::TurnContext, diff --git a/codex-rs/core/src/tools/js_repl/mod.rs b/codex-rs/core/src/tools/js_repl/mod.rs index 539c77385..ec4443aca 100644 --- a/codex-rs/core/src/tools/js_repl/mod.rs +++ b/codex-rs/core/src/tools/js_repl/mod.rs @@ -849,6 +849,10 @@ impl JsReplManager { .await } + #[expect( + clippy::await_holding_invalid_type, + reason = "js_repl kernel initialization must be serialized with kernel state" + )] pub async fn execute_with_cancellation( &self, session: Arc, @@ -1177,6 +1181,10 @@ impl JsReplManager { Ok(kernel_path) } + #[expect( + clippy::await_holding_invalid_type, + reason = "js_repl stdin writes must be serialized per kernel" + )] async fn write_message( stdin: &Arc>, msg: &HostToKernel, @@ -1224,6 +1232,10 @@ impl JsReplManager { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "js_repl child shutdown must serialize process inspection and termination" + )] async fn kill_kernel_child(child: &Arc>, reason: &'static str) { let mut guard = child.lock().await; let pid = guard.id(); @@ -1547,6 +1559,10 @@ impl JsReplManager { } } + #[expect( + clippy::await_holding_invalid_type, + reason = "nested js_repl tool routing reads through the session-owned manager guard" + )] async fn run_tool_request(exec: ExecContext, req: RunToolRequest) -> RunToolResult { if is_js_repl_internal_tool(&req.tool_name) { let error = "js_repl cannot invoke itself".to_string(); diff --git a/codex-rs/core/src/tools/registry.rs b/codex-rs/core/src/tools/registry.rs index 4c0e3fbea..aca1d1014 100644 --- a/codex-rs/core/src/tools/registry.rs +++ b/codex-rs/core/src/tools/registry.rs @@ -239,6 +239,10 @@ impl ToolRegistry { // } // } + #[expect( + clippy::await_holding_invalid_type, + reason = "tool dispatch must keep active-turn accounting atomic" + )] pub(crate) async fn dispatch_any( &self, invocation: ToolInvocation, diff --git a/codex-rs/core/src/tools/router_tests.rs b/codex-rs/core/src/tools/router_tests.rs index e466bdb1c..c83c65fc4 100644 --- a/codex-rs/core/src/tools/router_tests.rs +++ b/codex-rs/core/src/tools/router_tests.rs @@ -15,6 +15,10 @@ use super::ToolRouter; use super::ToolRouterParams; #[tokio::test] +#[expect( + clippy::await_holding_invalid_type, + reason = "test builds a router from session-owned MCP manager state" +)] async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> { let (session, mut turn) = make_session_and_context().await; turn.tools_config.js_repl_tools_only = true; @@ -70,6 +74,10 @@ async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> { } #[tokio::test] +#[expect( + clippy::await_holding_invalid_type, + reason = "test builds a router from session-owned MCP manager state" +)] async fn js_repl_tools_only_allows_js_repl_source_calls() -> anyhow::Result<()> { let (session, mut turn) = make_session_and_context().await; turn.tools_config.js_repl_tools_only = true; @@ -175,6 +183,10 @@ async fn js_repl_tools_only_blocks_namespaced_js_repl_tool() -> anyhow::Result<( } #[tokio::test] +#[expect( + clippy::await_holding_invalid_type, + reason = "test builds a router from session-owned MCP manager state" +)] async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow::Result<()> { let (session, turn) = make_session_and_context().await; let mcp_tools = session diff --git a/codex-rs/login/src/auth/external_bearer.rs b/codex-rs/login/src/auth/external_bearer.rs index c179f4861..c52859601 100644 --- a/codex-rs/login/src/auth/external_bearer.rs +++ b/codex-rs/login/src/auth/external_bearer.rs @@ -33,6 +33,10 @@ impl ExternalAuth for BearerTokenRefresher { AuthMode::ApiKey } + #[expect( + clippy::await_holding_invalid_type, + reason = "external bearer cache misses intentionally hold cached_token across the provider command to avoid duplicate refreshes" + )] async fn resolve(&self) -> io::Result> { let access_token = { let mut cached = self.state.cached_token.lock().await; diff --git a/codex-rs/rmcp-client/src/oauth.rs b/codex-rs/rmcp-client/src/oauth.rs index 979a7ad21..5adfae934 100644 --- a/codex-rs/rmcp-client/src/oauth.rs +++ b/codex-rs/rmcp-client/src/oauth.rs @@ -283,6 +283,10 @@ impl OAuthPersistor { /// Persists the latest stored credentials if they have changed. /// Deletes the credentials if they are no longer present. + #[expect( + clippy::await_holding_invalid_type, + reason = "AuthorizationManager async access must be serialized through its mutex" + )] pub(crate) async fn persist_if_needed(&self) -> Result<()> { let (client_id, maybe_credentials) = { let manager = self.inner.authorization_manager.clone(); @@ -335,6 +339,10 @@ impl OAuthPersistor { Ok(()) } + #[expect( + clippy::await_holding_invalid_type, + reason = "AuthorizationManager async access must be serialized through its mutex" + )] pub(crate) async fn refresh_if_needed(&self) -> Result<()> { let expires_at = { let guard = self.inner.last_credentials.lock().await;