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

4.9 KiB
Raw Blame History

DOs

  • Boldly pass overrides end-to-end: thread base_instructions through config → session → prompt.
// During ConfigureSession:
Op::ConfigureSession {
    // ...
    user_instructions,
    base_instructions: config.base_instructions.clone(),
    // ...
};

// When building the Prompt:
let prompt = Prompt {
    // ...
    user_instructions: sess.user_instructions.clone(),
    base_instructions_override: sess.base_instructions.clone(),
    // ...
};
  • Prefer helper utilities in tests to reduce flakiness and boilerplate.
// Good: wait for a specific event, then for TaskComplete.
let EventMsg::SessionConfigured(SessionConfiguredEvent { session_id, .. }) =
    test_support::wait_for_event(&codex, |ev| matches!(ev, EventMsg::SessionConfigured(_))).await
else { unreachable!() };

test_support::wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
  • Capture full return values from functions that now return more items; ignore what you dont need.
// Accept signature evolution safely:
let (codex, ..) = Codex::spawn(config, ctrl_c.clone()).await?;
// or explicitly:
let (codex, _init_id, _extra) = Codex::spawn(config, ctrl_c.clone()).await?;
  • Keep imports tidy after refactors; remove unused imports and the stray blank line they leave behind.
// Good: no unused imports and no extra blank lines.
use tempfile::TempDir;
use wiremock::{Mock, MockServer, ResponseTemplate};
  • Treat CodexToolCallParam.base_instructions as literal text (not a file path).
use codex_mcp_server::CodexToolCallParam;

let params = CodexToolCallParam {
    prompt: "How are you?".to_string(),
    base_instructions: Some("You are a helpful assistant.".to_string()),
    ..Default::default()
};
  • Support file-based overrides via config for local workflows.
# config.toml
experimental-instructions-file = "/abs/path/to/instructions.md"
fn get_base_instructions(path: Option<&PathBuf>) -> Option<String> {
    let path = path.as_ref()?;
    std::fs::read_to_string(path).ok()
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
}
  • Compose final instructions with the correct precedence: base first, then user.
let base = self.base_instructions_override.as_deref().unwrap_or(BASE_INSTRUCTIONS);
let mut sections = vec![base];
if let Some(ref user) = self.user_instructions {
    sections.push(user);
}
let full = sections.join("\n\n");
  • Write assertions that dont move Option values unnecessarily.
let maybe_id = Some(session_id.to_string());
assert!(maybe_id.is_some());
assert_eq!(request_body.to_str().unwrap(), maybe_id.as_ref().unwrap());
  • Verify overrides actually reach the wire in both APIs under test.
// responses API: body has top-level "instructions"
let body = request.body_json::<serde_json::Value>().unwrap();
assert!(body["instructions"].as_str().unwrap().contains("test instructions"));

// chat.completions API: first system message content
let body = request.body_json::<serde_json::Value>().unwrap();
let sys = body["messages"][0]["content"].as_str().unwrap();
assert!(sys.starts_with("You are a helpful assistant."));
  • Derive Default for test-friendly params.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct CodexToolCallParam { /* ... */ }

DONTs

  • Dont leave stale imports or extra blank lines after removing an import.
// Bad:
use tokio::time::timeout;

            // <- orphaned blank line after removing usage
  • Dont assume Codex::spawn still returns exactly two values.
// Bad: breaks if a third value was added.
let (codex, init_id) = Codex::spawn(config, ctrl_c).await?;
  • Dont pass file paths via base_instructions in the MCP tool call.
// Bad: this field expects literal instructions text, not a path.
base_instructions: Some("/tmp/instructions.md".to_string())
  • Dont move Option values you still need later.
// Bad:
assert_eq!(header, maybe_id.unwrap()); // moved, cannot use maybe_id again
  • Dont duplicate ad-hoc event polling loops in tests; use the shared helper.
// Bad: open-coded loop with timeouts and pattern matching everywhere.
loop {
    // ...
    if matches!(ev.msg, EventMsg::TaskComplete(_)) { break; }
}
  • Dont ignore the intended precedence: base instructions (built-in or override) must come before user instructions.
// Bad: user first, base second (reverses meaning).
let mut sections = vec![user, base];
  • Dont treat base_instructions and user_instructions as interchangeable; they serve different purposes.
// Bad: stuffing user content into base overrides changes global behavior.
config.base_instructions = config.user_instructions.clone();
  • Dont forget to trim and validate file-based overrides.
// Bad: accepts empty files and trailing whitespace verbatim.
std::fs::read_to_string(path).ok()