mirror of
https://github.com/openai/codex.git
synced 2026-04-28 10:21:06 +03:00
4.1 KiB
4.1 KiB
DOs
- Use one-liner mutex ops: Acquire the lock, do the operation, and let the guard drop immediately.
running_requests_id_to_codex_uuid
.lock()
.await
.insert(request_id.clone(), session_id);
// ...
running_requests_id_to_codex_uuid
.lock()
.await
.remove(&request_id);
- Handle error events decisively: On
EventMsg::Error, return a response, unregister, and break the loop.
match event {
EventMsg::Error(err) => {
outgoing
.send_response(request_id.clone(), serde_json::json!({ "error": err.message }))
.await;
running_requests_id_to_codex_uuid.lock().await.remove(&request_id);
break;
}
_ => { /* ... */ }
}
- Make notifications truly async: If the handler awaits, mark it
asyncand await at the call site.
// lib.rs
match msg {
JSONRPCMessage::Notification(n) => processor.process_notification(n).await,
_ => { /* ... */ }
}
// message_processor.rs
pub(crate) async fn process_notification(&mut self, n: JSONRPCNotification) { /* ... */ }
- Minimize lock scope and avoid nested locks: Read what you need, drop the guard, then lock the next map.
let session_id = {
let g = self.running_requests_id_to_codex_uuid.lock().await;
match g.get(&request_id).copied() { Some(id) => id, None => return }
};
let codex = {
let g = self.session_map.lock().await;
match g.get(&session_id).cloned() { Some(c) => c, None => return }
};
- Defer derived data until used: Only compute
request_id_stringwhere you actually need it.
let maybe_id = self.running_requests_id_to_codex_uuid.lock().await.get(&request_id).copied();
if maybe_id.is_none() {
let request_id_string = match &request_id {
RequestId::String(s) => s.clone(),
RequestId::Integer(i) => i.to_string(),
};
tracing::warn!("Session not found for request_id: {request_id_string}");
return;
}
- Write precise, portable tests: Assert JSON-RPC errors explicitly and make blocking commands cross‑platform.
// Match JSON-RPC error
match mcp_process.read_jsonrpc_message().await? {
JSONRPCMessage::Error(e) if e.id == RequestId::Integer(codex_request_id) => { /* ok */ }
other => anyhow::bail!("unexpected message: {other:?}"),
}
// Cross-platform blocking command
#[cfg(target_os = "windows")]
let shell_command = vec![
"powershell".to_string(), "-Command".to_string(), "Start-Sleep -Seconds 60".to_string()
];
#[cfg(not(target_os = "windows"))]
let shell_command = vec!["sleep".to_string(), "60".to_string()];
DON’Ts
- Don’t hold locks longer than necessary: Avoid temporary guard variables and extra braces that extend lifetimes.
// ❌ Holds the lock longer than needed
let mut guard = running.lock().await;
guard.insert(request_id.clone(), session_id);
// guard lives until end of scope
- Don’t ignore cleanup on all exit paths: Always unregister request IDs on success, error, and submit failures.
// ❌ Missing cleanup on error
if let Err(e) = codex.submit_with_id(submission).await {
tracing::error!("submit failed: {e}");
// running_requests_id_to_codex_uuid.remove(...) is missing
}
- Don’t treat MCP errors as “responses”: Don’t assert on
JSONRPCResponsewith null/embedded error; matchJSONRPCMessage::Error.
// ❌ Fragile: assumes error tunneled as a response payload
let JSONRPCMessage::Response(r) = msg else { /* ... */ };
assert!(r.result.get("error").is_some());
- Don’t precompute derived strings you may not use: Compute
request_id_stringonly in the branches that need it.
// ❌ Work done upfront even if early-return
let request_id_string = match &request_id { /* ... */ };
// early return before using request_id_string
- Don’t blanket-allow dead code/imports: Prefer targeted allowances or remove unused helpers.
// ❌ Blanket allow across a module
#![allow(dead_code, unused_imports)]
// ✅ Narrow allowance for a single helper used by some tests
#[allow(dead_code)]
async fn read_stream_until_error(/* ... */) { /* ... */ }