Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cfef81b
Route AGENTS.md loading through environment filesystems
anp-oai Jun 2, 2026
51f8c65
Expose source-less loaded user instructions
anp-oai Jun 2, 2026
73dd574
Restore AGENTS.md source discovery API
anp-oai Jun 2, 2026
18ad9cb
Treat empty loaded instructions as absent
anp-oai Jun 2, 2026
dcd6d1e
Use LoadedAgentsMd directly in integration tests
anp-oai Jun 2, 2026
a4ea9ff
Separate child AGENTS.md source coverage
anp-oai Jun 2, 2026
d847029
Name the user AGENTS.md constructor explicitly
anp-oai Jun 2, 2026
eb4eca2
Mark source-less AGENTS.md construction as test-only
anp-oai Jun 2, 2026
df7f33b
Treat whitespace-only loaded instructions as empty
anp-oai Jun 2, 2026
6eab3d8
Explain the project instruction separator boundary
anp-oai Jun 2, 2026
5a58afb
Preserve instruction source discovery behavior
anp-oai Jun 3, 2026
bb8f8d7
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
10ee4a8
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
daa2e6a
Use cached AGENTS.md instruction sources
anp-oai Jun 3, 2026
26f4cf6
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
3303a00
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
952b8b8
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
8a65626
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
4a20577
codex: address PR review feedback (#26205)
anp-oai Jun 3, 2026
169c298
codex: fix CI failure on PR #26205
anp-oai Jun 3, 2026
7ab00db
codex: address PR review feedback (#26205)
anp-oai Jun 4, 2026
32cf518
codex: avoid canonicalizing AGENTS.md cwd
anp-oai Jun 4, 2026
8363196
codex: canonicalize AGENTS.md cwd through environment fs
anp-oai Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions codex-rs/app-server/src/request_processors/thread_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,12 +659,6 @@ impl ThreadRequestProcessor {
.map(|response| Some(response.into()))
}

async fn instruction_sources_from_config(config: &Config) -> Vec<AbsolutePathBuf> {
codex_core::AgentsMdManager::new(config)
.instruction_sources(LOCAL_FS.as_ref())
.await
}

async fn load_thread(
&self,
thread_id: &str,
Expand Down Expand Up @@ -1043,7 +1037,6 @@ impl ThreadRequestProcessor {
.map_err(|err| config_load_error(&err))?;
}

let instruction_sources = Self::instruction_sources_from_config(&config).await;
let environments = environments.unwrap_or_else(|| {
listener_task_context
.thread_manager
Expand Down Expand Up @@ -1113,6 +1106,7 @@ impl ThreadRequestProcessor {
)
.await?;

let instruction_sources = thread.instruction_sources().await;
let config_snapshot = thread
.config_snapshot()
.instrument(tracing::info_span!(
Expand Down Expand Up @@ -2524,13 +2518,12 @@ impl ThreadRequestProcessor {
}
};

let instruction_sources = Self::instruction_sources_from_config(&config).await;
let response_history = thread_history.clone();

match self
.thread_manager
.resume_thread_with_history(
config.clone(),
config,
thread_history,
self.auth_manager.clone(),
self.request_trace_context(&request_id).await,
Expand All @@ -2553,6 +2546,7 @@ impl ThreadRequestProcessor {
self.outgoing.send_error(request_id, err).await;
return Ok(());
}
let instruction_sources = codex_thread.instruction_sources().await;
let SessionConfiguredEvent { rollout_path, .. } = session_configured;
let Some(rollout_path) = rollout_path else {
let error =
Expand Down Expand Up @@ -2853,10 +2847,7 @@ impl ThreadRequestProcessor {
/*include_turns*/ false,
);
thread_summary.session_id = existing_thread.session_configured().session_id.to_string();
let mut config_for_instruction_sources = self.config.as_ref().clone();
config_for_instruction_sources.cwd = config_snapshot.cwd.clone();
let instruction_sources =
Self::instruction_sources_from_config(&config_for_instruction_sources).await;
let instruction_sources = existing_thread.instruction_sources().await;

let listener_command_tx = {
let thread_state = thread_state.lock().await;
Expand Down Expand Up @@ -3233,7 +3224,6 @@ impl ThreadRequestProcessor {
.map_err(|err| config_load_error(&err))?;

let fallback_model_provider = config.model_provider_id.clone();
let instruction_sources = Self::instruction_sources_from_config(&config).await;

let NewThread {
thread_id,
Expand Down Expand Up @@ -3284,6 +3274,8 @@ impl ThreadRequestProcessor {
.map_err(|err| core_thread_write_error("inherit source thread name", err))?;
}

let instruction_sources = forked_thread.instruction_sources().await;

// Auto-attach a conversation listener when forking a thread.
log_listener_attach_result(
self.ensure_conversation_listener(
Expand Down
76 changes: 76 additions & 0 deletions codex-rs/app-server/tests/suite/v2/thread_resume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,82 @@ async fn thread_resume_with_empty_path_uses_running_thread_id() -> Result<()> {
Ok(())
}

#[tokio::test]
async fn thread_resume_running_thread_uses_cached_instruction_sources() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri())?;
let workspace = TempDir::new()?;
let project_agents = workspace.path().join("AGENTS.md");
std::fs::write(&project_agents, "project instructions")?;

let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;

let start_id = mcp
.send_thread_start_request(ThreadStartParams {
cwd: Some(workspace.path().display().to_string()),
..Default::default()
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse {
thread,
instruction_sources,
..
} = to_response::<ThreadStartResponse>(start_resp)?;
let project_agents = AbsolutePathBuf::try_from(std::fs::canonicalize(project_agents)?)?;
assert_eq!(instruction_sources, vec![project_agents.clone()]);

let turn_id = mcp
.send_turn_start_request(TurnStartParams {
thread_id: thread.id.clone(),
client_user_message_id: None,
input: vec![UserInput::Text {
text: "materialize rollout".to_string(),
text_elements: Vec::new(),
}],
..Default::default()
})
.await?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(turn_id)),
)
.await??;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("turn/completed"),
)
.await??;

std::fs::remove_file(project_agents.as_path())?;

let resume_id = mcp
.send_thread_resume_request(ThreadResumeParams {
thread_id: thread.id,
..Default::default()
})
.await?;
let resume_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(resume_id)),
)
.await??;
let ThreadResumeResponse {
instruction_sources,
..
} = to_response::<ThreadResumeResponse>(resume_resp)?;

assert_eq!(instruction_sources, vec![project_agents]);

Ok(())
}

#[tokio::test]
async fn turn_start_updates_runtime_workspace_roots_for_loaded_thread() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
Expand Down
77 changes: 77 additions & 0 deletions codex-rs/app-server/tests/suite/v2/thread_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,83 @@ async fn thread_start_response_includes_loaded_instruction_sources() -> Result<(
Ok(())
}

#[tokio::test]
async fn thread_start_response_excludes_empty_project_instruction_source() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml_without_approval_policy(codex_home.path(), &server.uri())?;
let global_agents_path = codex_home.path().join("AGENTS.md");
std::fs::write(&global_agents_path, "global instructions")?;
let workspace = TempDir::new()?;
let project_agents_path = workspace.path().join("AGENTS.md");
std::fs::write(project_agents_path, "")?;

let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;

let request_id = mcp
.send_thread_start_request(ThreadStartParams {
cwd: Some(workspace.path().display().to_string()),
..Default::default()
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let ThreadStartResponse {
instruction_sources,
..
} = to_response::<ThreadStartResponse>(response)?;

let instruction_sources = instruction_sources
.into_iter()
.map(normalize_path_for_comparison)
.collect::<Vec<_>>();
let expected_instruction_sources = vec![normalize_path_for_comparison(std::fs::canonicalize(
global_agents_path,
)?)];

assert_eq!(instruction_sources, expected_instruction_sources);

Ok(())
}

#[tokio::test]
async fn thread_start_without_selected_environment_excludes_instruction_sources() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml_without_approval_policy(codex_home.path(), &server.uri())?;
std::fs::write(codex_home.path().join("AGENTS.md"), "global instructions")?;
let workspace = TempDir::new()?;
std::fs::write(workspace.path().join("AGENTS.md"), "project instructions")?;

let mut mcp = TestAppServer::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;

let request_id = mcp
.send_thread_start_request(ThreadStartParams {
cwd: Some(workspace.path().display().to_string()),
environments: Some(Vec::new()),
..Default::default()
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let ThreadStartResponse {
instruction_sources,
..
} = to_response::<ThreadStartResponse>(response)?;

assert!(instruction_sources.is_empty());

Ok(())
}

#[cfg(windows)]
fn normalize_path_for_comparison(path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub use codex_config::types::TuiPetAnchor;
pub use codex_config::types::UriBasedFileOpener;
pub use codex_core::CodexThread;
pub use codex_core::ForkSnapshot;
pub use codex_core::LoadedAgentsMd;
pub use codex_core::McpManager;
pub use codex_core::NewThread;
pub use codex_core::StartThreadOptions;
Expand Down
Loading
Loading