Show global AGENTS.md in /status (#17091)

Addresses #3793

Problem: /status only reported project-level AGENTS files, so sessions
with a loaded global $CODEX_HOME/AGENTS.md still showed Agents.md as
<none>.

Solution: Track the global instructions file loaded during config
initialization and prepend that path to the /status Agents.md summary,
with coverage for AGENTS.md, AGENTS.override.md, and global-plus-project
ordering.
This commit is contained in:
Eric Traut
2026-04-08 09:04:32 -07:00
committed by GitHub
Unverified
parent 4c07dd4d25
commit dc5feb916d
3 changed files with 147 additions and 3 deletions
+54
View File
@@ -128,6 +128,56 @@ fn load_config_normalizes_relative_cwd_override() -> std::io::Result<()> {
Ok(())
}
#[test]
fn load_config_records_global_agents_path() -> std::io::Result<()> {
let codex_home = tempdir()?;
let global_agents_path = codex_home.path().join(DEFAULT_PROJECT_DOC_FILENAME);
std::fs::write(&global_agents_path, "\n global instructions \n")?;
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides::default(),
codex_home.abs().into_path_buf(),
)?;
assert_eq!(
config.user_instructions.as_deref(),
Some("global instructions")
);
assert_eq!(
config.user_instructions_path.as_deref(),
Some(global_agents_path.as_path())
);
Ok(())
}
#[test]
fn load_config_records_preferred_global_agents_override_path() -> std::io::Result<()> {
let codex_home = tempdir()?;
std::fs::write(
codex_home.path().join(DEFAULT_PROJECT_DOC_FILENAME),
"global instructions",
)?;
let global_agents_override_path = codex_home.path().join(LOCAL_PROJECT_DOC_FILENAME);
std::fs::write(&global_agents_override_path, "local override instructions")?;
let config = Config::load_from_base_config_with_overrides(
ConfigToml::default(),
ConfigOverrides::default(),
codex_home.abs().into_path_buf(),
)?;
assert_eq!(
config.user_instructions.as_deref(),
Some("local override instructions")
);
assert_eq!(
config.user_instructions_path.as_deref(),
Some(global_agents_override_path.as_path())
);
Ok(())
}
#[test]
fn test_toml_parsing() {
let history_with_persistence = r#"
@@ -4457,6 +4507,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
user_instructions_path: None,
notify: None,
cwd: fixture.cwd(),
cli_auth_credentials_store_mode: Default::default(),
@@ -4603,6 +4654,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
user_instructions_path: None,
notify: None,
cwd: fixture.cwd(),
cli_auth_credentials_store_mode: Default::default(),
@@ -4747,6 +4799,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
user_instructions_path: None,
notify: None,
cwd: fixture.cwd(),
cli_auth_credentials_store_mode: Default::default(),
@@ -4877,6 +4930,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
approvals_reviewer: ApprovalsReviewer::User,
enforce_residency: Constrained::allow_any(/*initial_value*/ None),
user_instructions: None,
user_instructions_path: None,
notify: None,
cwd: fixture.cwd(),
cli_auth_credentials_store_mode: Default::default(),
+18 -3
View File
@@ -245,6 +245,9 @@ pub struct Config {
/// User-provided instructions from AGENTS.md.
pub user_instructions: Option<String>,
/// Path to the global AGENTS file loaded into `user_instructions`.
pub user_instructions_path: Option<PathBuf>,
/// Base instructions override.
pub base_instructions: Option<String>,
@@ -1408,7 +1411,10 @@ impl Config {
network: network_requirements,
} = config_layer_stack.requirements().clone();
let user_instructions = Self::load_instructions(Some(&codex_home));
let (user_instructions, user_instructions_path) =
Self::load_instructions(Some(&codex_home))
.map(|loaded| (Some(loaded.contents), Some(loaded.path)))
.unwrap_or((None, None));
let mut startup_warnings = Vec::new();
// Destructure ConfigOverrides fully to ensure all overrides are applied.
@@ -2001,6 +2007,7 @@ impl Config {
enforce_residency: enforce_residency.value,
notify: cfg.notify,
user_instructions,
user_instructions_path,
base_instructions,
personality,
developer_instructions,
@@ -2168,7 +2175,7 @@ impl Config {
Ok(config)
}
fn load_instructions(codex_dir: Option<&Path>) -> Option<String> {
fn load_instructions(codex_dir: Option<&Path>) -> Option<LoadedUserInstructions> {
let base = codex_dir?;
for candidate in [LOCAL_PROJECT_DOC_FILENAME, DEFAULT_PROJECT_DOC_FILENAME] {
let mut path = base.to_path_buf();
@@ -2176,7 +2183,10 @@ impl Config {
if let Ok(contents) = std::fs::read_to_string(&path) {
let trimmed = contents.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
return Some(LoadedUserInstructions {
contents: trimmed.to_string(),
path,
});
}
}
}
@@ -2250,6 +2260,11 @@ impl Config {
}
}
struct LoadedUserInstructions {
contents: String,
path: PathBuf,
}
pub(crate) fn uses_deprecated_instructions_file(config_layer_stack: &ConfigLayerStack) -> bool {
config_layer_stack
.layers_high_to_low()
+75
View File
@@ -43,6 +43,10 @@ pub(crate) async fn discover_agents_summary(config: &Config) -> io::Result<Strin
pub(crate) fn compose_agents_summary(config: &Config, paths: &[AbsolutePathBuf]) -> String {
let mut rels: Vec<String> = Vec::new();
if let Some(path) = config.user_instructions_path.as_deref() {
rels.push(format_directory_display(path, /*max_width*/ None));
}
for p in paths {
let file_name = p
.file_name()
@@ -189,7 +193,21 @@ fn title_case(s: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
use codex_core::DEFAULT_PROJECT_DOC_FILENAME;
use codex_core::LOCAL_PROJECT_DOC_FILENAME;
use codex_core::config::ConfigBuilder;
use pretty_assertions::assert_eq;
use std::fs;
use tempfile::TempDir;
async fn test_config(codex_home: &TempDir, cwd: &TempDir) -> Config {
ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(cwd.path().to_path_buf()))
.build()
.await
.expect("load config")
}
#[test]
fn plan_type_display_name_remaps_display_labels() {
@@ -211,4 +229,61 @@ mod tests {
assert_eq!(plan_type_display_name(plan_type), expected);
}
}
#[tokio::test]
async fn discover_agents_summary_includes_global_agents_path() {
let codex_home = TempDir::new().expect("temp codex home");
let cwd = TempDir::new().expect("temp cwd");
let global_agents_path = codex_home.path().join(DEFAULT_PROJECT_DOC_FILENAME);
fs::write(&global_agents_path, "global instructions").expect("write global agents");
let config = test_config(&codex_home, &cwd).await;
assert_eq!(
discover_agents_summary(&config).await.expect("summary"),
format_directory_display(&global_agents_path, /*max_width*/ None)
);
}
#[tokio::test]
async fn discover_agents_summary_names_global_agents_override() {
let codex_home = TempDir::new().expect("temp codex home");
let cwd = TempDir::new().expect("temp cwd");
fs::write(
codex_home.path().join(DEFAULT_PROJECT_DOC_FILENAME),
"global instructions",
)
.expect("write global agents");
let override_path = codex_home.path().join(LOCAL_PROJECT_DOC_FILENAME);
fs::write(&override_path, "override instructions").expect("write global override");
let config = test_config(&codex_home, &cwd).await;
assert_eq!(
discover_agents_summary(&config).await.expect("summary"),
format_directory_display(&override_path, /*max_width*/ None)
);
}
#[tokio::test]
async fn discover_agents_summary_orders_global_before_project_agents() {
let codex_home = TempDir::new().expect("temp codex home");
let cwd = TempDir::new().expect("temp cwd");
let global_agents_path = codex_home.path().join(DEFAULT_PROJECT_DOC_FILENAME);
fs::write(&global_agents_path, "global instructions").expect("write global agents");
fs::write(
cwd.path().join(DEFAULT_PROJECT_DOC_FILENAME),
"project instructions",
)
.expect("write project agents");
let config = test_config(&codex_home, &cwd).await;
let summary = discover_agents_summary(&config).await.expect("summary");
let mut paths = summary.split(", ");
assert_eq!(
paths.next(),
Some(format_directory_display(&global_agents_path, /*max_width*/ None).as_str())
);
let project_path = paths.next().expect("project agents path");
assert!(project_path.ends_with(DEFAULT_PROJECT_DOC_FILENAME));
assert_eq!(paths.next(), None);
}
}