Files
codex/prs/bolinfest/study/PR-1646-study.md
2025-09-02 15:17:45 -07:00

4.1 KiB
Raw Blame History

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 async and 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_string where 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 crossplatform.
// 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()];

DONTs

  • Dont 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
  • Dont 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
}
  • Dont treat MCP errors as “responses”: Dont assert on JSONRPCResponse with null/embedded error; match JSONRPCMessage::Error.
// ❌ Fragile: assumes error tunneled as a response payload
let JSONRPCMessage::Response(r) = msg else { /* ... */ };
assert!(r.result.get("error").is_some());
  • Dont precompute derived strings you may not use: Compute request_id_string only 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
  • Dont 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(/* ... */) { /* ... */ }