Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 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 @@ -1840,6 +1840,11 @@ fn create_config_toml_with_profile_workspace_root(
[permissions.dev.workspace_roots]
"{profile_root_key}" = true

# This test only exercises workspace roots; Windows restricted-token sandboxes
# cannot enforce a filesystem policy without root read access.
[permissions.dev.filesystem]
":root" = "read"

[permissions.dev.filesystem.":workspace_roots"]
"." = "write"
"#,
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/app-server/tests/suite/v2/turn_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2828,6 +2828,11 @@ wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0

# This test only exercises writable workspace roots; the Windows restricted-token
# sandbox cannot enforce a filesystem policy without root read access.
[permissions.dev.filesystem]
":root" = "read"

[permissions.dev.filesystem.":workspace_roots"]
"." = "write"
"#
Expand Down
52 changes: 43 additions & 9 deletions codex-rs/core/src/agents_md.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@
use crate::config::Config;
use crate::context::UserInstructions as ContextUserInstructions;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::tools::sandboxing::executor_windows_sandbox_level;
use codex_config::ConfigLayerSource;
use codex_config::default_project_root_markers;
use codex_config::merge_toml_values;
use codex_config::project_root_markers_from_config;
use codex_exec_server::ExecutorFileSystem;
use codex_extension_api::UserInstructions;
use codex_file_system::FileSystemSandboxContext;
use codex_file_system::FindUpErrorPolicy;
use codex_file_system::find_nearest_ancestor_with_markers;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use futures::StreamExt;
Expand Down Expand Up @@ -53,7 +56,8 @@ pub(crate) async fn load_project_instructions(
config: &Config,
user_instructions: Option<UserInstructions>,
environments: &TurnEnvironmentSnapshot,
) -> Option<LoadedAgentsMd> {
windows_sandbox_level: WindowsSandboxLevel,
) -> io::Result<Option<LoadedAgentsMd>> {
let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions);
let mut remaining = config.project_doc_max_bytes;
for turn_environment in environments.turn_environments() {
Expand All @@ -62,12 +66,31 @@ pub(crate) async fn load_project_instructions(
}

let filesystem = turn_environment.environment.get_filesystem();
let sandbox = (!turn_environment
.permission_profile()
.file_system_sandbox_policy()
.has_full_disk_read_access())
.then(|| {
// TODO(anp): Move sandbox context construction to a method on TurnEnvironment.
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
turn_environment.permission_profile().clone(),
turn_environment.cwd().clone(),
);
sandbox.workspace_roots = turn_environment.workspace_roots().to_vec();
sandbox.windows_sandbox_level =
executor_windows_sandbox_level(windows_sandbox_level, turn_environment.cwd());
sandbox.windows_sandbox_private_desktop =
config.permissions.windows_sandbox_private_desktop;
sandbox.use_legacy_landlock = config.features.use_legacy_landlock();
sandbox
});
match read_agents_md(
config,
filesystem.as_ref(),
&turn_environment.selection.environment_id,
turn_environment.cwd(),
remaining,
sandbox.as_ref(),
)
.await
{
Expand All @@ -78,16 +101,25 @@ pub(crate) async fn load_project_instructions(
}
}
Ok(None) => {}
Err(e) => {
Err(error) if sandbox.is_none() => {
error!(
environment_id = turn_environment.selection.environment_id,
"error trying to find AGENTS.md docs: {e:#}"
"error trying to find AGENTS.md docs: {error:#}"
);
}
Err(error) => {
return Err(io::Error::new(
error.kind(),
format!(
"failed to load AGENTS.md instructions for environment `{}`: {error}",
turn_environment.selection.environment_id
),
));
}
}
}

(!loaded.is_empty()).then_some(loaded)
Ok((!loaded.is_empty()).then_some(loaded))
}

/// Attempt to locate and load AGENTS.md documentation.
Expand All @@ -102,12 +134,13 @@ async fn read_agents_md(
environment_id: &str,
cwd: &PathUri,
max_total: usize,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Option<LoadedAgentsMd>> {
if max_total == 0 {
return Ok(None);
}

let paths = agents_md_paths(config, cwd, fs).await?;
let paths = agents_md_paths(config, cwd, fs, sandbox).await?;
if paths.is_empty() {
return Ok(None);
}
Expand All @@ -120,7 +153,7 @@ async fn read_agents_md(
break;
}

let mut data = match fs.read_file(&p, /*sandbox*/ None).await {
let mut data = match fs.read_file(&p, sandbox).await {
Ok(data) => data,
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(err) => return Err(err),
Expand Down Expand Up @@ -165,6 +198,7 @@ async fn agents_md_paths(
config: &Config,
cwd: &PathUri,
fs: &dyn ExecutorFileSystem,
sandbox: Option<&FileSystemSandboxContext>,
) -> io::Result<Vec<PathUri>> {
let dir = cwd.clone();

Expand All @@ -187,8 +221,8 @@ async fn agents_md_paths(
fs,
&dir,
project_root_markers,
FindUpErrorPolicy::Propagate,
/*sandbox*/ None,
FindUpErrorPolicy::Ignore,
sandbox,
)
.await?;
let search_dirs = if let Some(root) = project_root {
Expand Down Expand Up @@ -218,7 +252,7 @@ async fn agents_md_paths(
let candidate = directory
.join(name)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
match fs.get_metadata(&candidate, /*sandbox*/ None).await {
match fs.get_metadata(&candidate, sandbox).await {
Ok(metadata) if metadata.is_file => return Ok(Some(candidate)),
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Expand Down
41 changes: 33 additions & 8 deletions codex-rs/core/src/agents_md_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::agents_md::load_project_instructions;
use crate::config::Config;
use crate::environment_selection::TurnEnvironmentSnapshot;
use codex_extension_api::UserInstructions;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::TurnEnvironmentSelection;
use std::io;
use std::sync::Arc;
use tokio::sync::Mutex;

Expand All @@ -16,6 +18,7 @@ pub(crate) struct AgentsMdManager {
#[derive(Default)]
struct AgentsMdCache {
selections: Option<Vec<TurnEnvironmentSelection>>,
windows_sandbox_level: Option<WindowsSandboxLevel>,
loaded: Option<Arc<LoadedAgentsMd>>,
}

Expand All @@ -29,19 +32,41 @@ impl AgentsMdManager {
}

#[tracing::instrument(name = "agents_md.refresh", skip_all)]
pub(crate) async fn refresh(&self, config: &Config, environments: &TurnEnvironmentSnapshot) {
let selections = environments.to_selections();
if self.cache.lock().await.selections.as_ref() == Some(&selections) {
return;
pub(crate) async fn refresh(
&self,
config: &Config,
environments: &TurnEnvironmentSnapshot,
windows_sandbox_level: WindowsSandboxLevel,
) -> io::Result<()> {
let selections = environments
.turn_environments()
.map(|environment| environment.selection.clone())
.collect::<Vec<_>>();
{
let mut cache = self.cache.lock().await;
if cache.selections.as_ref() == Some(&selections)
&& cache.windows_sandbox_level == Some(windows_sandbox_level)
{
return Ok(());
}
cache.selections = None;
cache.windows_sandbox_level = None;
cache.loaded = None;
}

let loaded =
load_project_instructions(config, self.user_instructions.clone(), environments)
.await
.map(Arc::new);
let loaded = load_project_instructions(
config,
self.user_instructions.clone(),
environments,
windows_sandbox_level,
)
.await?
.map(Arc::new);
let mut cache = self.cache.lock().await;
cache.selections = Some(selections);
cache.windows_sandbox_level = Some(windows_sandbox_level);
cache.loaded = loaded;
Ok(())
}

pub(crate) async fn get_loaded(&self) -> Option<Arc<LoadedAgentsMd>> {
Expand Down
Loading
Loading