From 9cd8115a6974af34634af9a76077828071ff29f7 Mon Sep 17 00:00:00 2001 From: Adam Perry Date: Sat, 6 Jun 2026 16:52:25 -0700 Subject: [PATCH] test: cover reviewed instruction edge cases --- .../app-server/tests/suite/v2/thread_fork.rs | 58 ++++++++++++++++++- .../core/tests/suite/global_instructions.rs | 15 +++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index 1dc07963ad..ad6665eaa2 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -37,6 +37,7 @@ use codex_rollout::append_rollout_item_to_path; use codex_rollout::append_thread_name; use codex_rollout::read_session_meta_line; use codex_utils_absolute_path::AbsolutePathBuf; +use core_test_support::responses; use pretty_assertions::assert_eq; use serde_json::Value; use serde_json::json; @@ -252,7 +253,21 @@ async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> { #[tokio::test] async fn thread_fork_reloads_instruction_sources() -> Result<()> { - let server = create_mock_responses_server_repeating_assistant("Done").await; + let server = responses::start_mock_server().await; + let response_mock = responses::mount_sse_sequence( + &server, + vec![ + responses::sse(vec![ + responses::ev_response_created("parent-response"), + responses::ev_completed("parent-response"), + ]), + responses::sse(vec![ + responses::ev_response_created("fork-response"), + responses::ev_completed("fork-response"), + ]), + ], + ) + .await; let codex_home = TempDir::new()?; create_config_toml(codex_home.path(), &server.uri())?; let global_agents = codex_home.path().join("AGENTS.md"); @@ -322,6 +337,7 @@ async fn thread_fork_reloads_instruction_sources() -> Result<()> { ) .await??; let ThreadForkResponse { + thread: forked_thread, instruction_sources, .. } = to_response::(fork_resp)?; @@ -332,6 +348,46 @@ async fn thread_fork_reloads_instruction_sources() -> Result<()> { "fork reloads sources after the files have been removed" ); + let fork_turn_id = mcp + .send_turn_start_request(TurnStartParams { + thread_id: forked_thread.id, + input: vec![UserInput::Text { + text: "inspect inherited instructions".to_string(), + text_elements: Vec::new(), + }], + ..Default::default() + }) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(fork_turn_id)), + ) + .await??; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let expected_instruction = format!( + "# AGENTS.md instructions for {}\n\n\nglobal instructions\n\n--- project-doc ---\n\nproject instructions\n", + workspace.path().display() + ); + let requests = response_mock.requests(); + assert_eq!(requests.len(), 2); + for request in requests { + let instruction_fragments = request + .message_input_texts("user") + .into_iter() + .filter(|text| text.starts_with("# AGENTS.md instructions for ")) + .collect::>(); + assert_eq!( + instruction_fragments, + vec![expected_instruction.clone()], + "parent and fork requests should contain the same historical instruction fragment" + ); + } + Ok(()) } diff --git a/codex-rs/core/tests/suite/global_instructions.rs b/codex-rs/core/tests/suite/global_instructions.rs index 8da620d23b..b9fb68ead3 100644 --- a/codex-rs/core/tests/suite/global_instructions.rs +++ b/codex-rs/core/tests/suite/global_instructions.rs @@ -176,6 +176,14 @@ async fn fresh_thread_composes_global_before_project_and_reports_sources() -> Re #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn global_loading_warning_surfaces_during_thread_creation() -> Result<()> { let server = responses::start_mock_server().await; + let response_mock = responses::mount_sse_once( + &server, + responses::sse(vec![ + responses::ev_response_created("warning-response"), + responses::ev_completed("warning-response"), + ]), + ) + .await; let home = Arc::new(TempDir::new()?); let source = write_global(home.as_ref(), b"global\xFFinstructions")?; @@ -197,10 +205,17 @@ async fn global_loading_warning_surfaces_during_thread_creation() -> Result<()> warning.contains("invalid UTF-8"), "expected warning to contain \"invalid UTF-8\"; observed: {warning}" ); + test.submit_turn("inspect lossy global instructions") + .await?; + let expected_fragment = + expected_instruction_fragment(&test.config.cwd, "global\u{FFFD}instructions"); + assert_single_instruction_fragment(&response_mock.single_request(), &expected_fragment); Ok(()) } +// TODO(anp): Align cold-resume instruction sources with the historical instructions replayed to +// the model so the API source list and model-visible context describe the same files. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn cold_resume_replays_rendered_instructions_but_reports_current_config_sources() -> Result<()> {