Stream executor capability and skill file reads (#39620)

## What changed

- Use `ExecutorFileSystem::read_file_stream` for capability files and executor skill resources regardless of whether filesystem sandboxing is active.
- Enforce per-file and bundle size limits incrementally as chunks arrive.
- Preserve the existing error for Windows resources when the required filesystem sandbox is unavailable.

GitOrigin-RevId: 1a40602a8f913588ec9a6ad7edd2d62eb82436e1
This commit is contained in:
Adam Perry @ OpenAI
2026-08-19 21:46:34 +00:00
committed by copyberry
parent da6e68951b
commit f1e06b3865
2 changed files with 40 additions and 77 deletions

View File

@@ -354,49 +354,32 @@ async fn read_optional_text_file(
));
return None;
}
let contents = if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) {
match file_system.read_file(&path, sandbox).await {
Ok(contents) if contents.len() <= MAX_FILE_BYTES && budget.can_add(contents.len()) => {
contents
}
Ok(_) => {
warnings.push(format!("capability file {path} exceeded its read limit"));
return None;
}
Err(error) => {
warnings.push(format!("failed to read capability file {path}: {error}"));
return None;
}
let mut stream = match file_system.read_file_stream(&path, sandbox).await {
Ok(stream) => stream,
Err(error) => {
warnings.push(format!("failed to read capability file {path}: {error}"));
return None;
}
} else {
let mut stream = match file_system.read_file_stream(&path, sandbox).await {
Ok(stream) => stream,
};
let mut contents = Vec::with_capacity(size);
while let Some(chunk) = stream.next().await {
let chunk = match chunk {
Ok(chunk) => chunk,
Err(error) => {
warnings.push(format!("failed to read capability file {path}: {error}"));
return None;
}
};
let mut contents = Vec::with_capacity(size);
while let Some(chunk) = stream.next().await {
let chunk = match chunk {
Ok(chunk) => chunk,
Err(error) => {
warnings.push(format!("failed to read capability file {path}: {error}"));
return None;
}
};
let Some(new_len) = contents.len().checked_add(chunk.len()) else {
warnings.push(format!("capability file {path} exceeded its read limit"));
return None;
};
if new_len > MAX_FILE_BYTES || !budget.can_add(new_len) {
warnings.push(format!("capability file {path} exceeded its read limit"));
return None;
}
contents.extend_from_slice(&chunk);
let Some(new_len) = contents.len().checked_add(chunk.len()) else {
warnings.push(format!("capability file {path} exceeded its read limit"));
return None;
};
if new_len > MAX_FILE_BYTES || !budget.can_add(new_len) {
warnings.push(format!("capability file {path} exceeded its read limit"));
return None;
}
contents
};
contents.extend_from_slice(&chunk);
}
let contents = match String::from_utf8(contents) {
Ok(contents) => contents,
Err(error) => {

View File

@@ -302,51 +302,31 @@ async fn read_bounded_text(
"failed to read executor skill resource {resource}: {err}"
))
};
let contents = if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox) {
if path.infer_path_convention() == Some(PathConvention::Windows)
&& sandbox.is_some_and(|context| {
context.windows_sandbox_level
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
})
{
return Err(SkillProviderError::new(
"executor skill resource requires an unavailable filesystem sandbox",
));
}
let metadata = file_system
.get_metadata(path, sandbox)
.await
.map_err(&read_error)?;
if metadata.size > MAX_SKILL_RESOURCE_CONTENT_BYTES as u64 {
if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox)
&& path.infer_path_convention() == Some(PathConvention::Windows)
&& sandbox.is_some_and(|context| {
context.windows_sandbox_level
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
})
{
return Err(SkillProviderError::new(
"executor skill resource requires an unavailable filesystem sandbox",
));
}
let mut stream = file_system
.read_file_stream(path, sandbox)
.await
.map_err(&read_error)?;
let mut contents = Vec::new();
while let Some(chunk) = stream.next().await {
let chunk = chunk.map_err(&read_error)?;
if contents.len().saturating_add(chunk.len()) > MAX_SKILL_RESOURCE_CONTENT_BYTES {
return Err(SkillProviderError::new(format!(
"executor skill resource {resource} exceeds {MAX_SKILL_RESOURCE_CONTENT_BYTES} bytes"
)));
}
file_system
.read_file(path, sandbox)
.await
.map_err(&read_error)?
} else {
let mut stream = file_system
.read_file_stream(path, sandbox)
.await
.map_err(&read_error)?;
let mut contents = Vec::new();
while let Some(chunk) = stream.next().await {
let chunk = chunk.map_err(&read_error)?;
if contents.len().saturating_add(chunk.len()) > MAX_SKILL_RESOURCE_CONTENT_BYTES {
return Err(SkillProviderError::new(format!(
"executor skill resource {resource} exceeds {MAX_SKILL_RESOURCE_CONTENT_BYTES} bytes"
)));
}
contents.extend_from_slice(&chunk);
}
contents
};
if contents.len() > MAX_SKILL_RESOURCE_CONTENT_BYTES {
return Err(SkillProviderError::new(format!(
"executor skill resource {resource} exceeds {MAX_SKILL_RESOURCE_CONTENT_BYTES} bytes"
)));
contents.extend_from_slice(&chunk);
}
String::from_utf8(contents).map_err(|_| {
SkillProviderError::new(format!(