[codex] default unknown contributed tools to mutating (#22143)

## Summary
- make the shared `ToolExecutor::is_mutating` default conservative by
returning `true`
- update the trait docs to say read-only tools should opt out explicitly
- add a regression test covering the default behavior

## Why
Hosts use this signal for serialization and approval policy. Treating
unknown contributed tools as read-only lets a write-capable tool
accidentally bypass mutating-tool safeguards if it forgets to override
the hook.

## Validation
- not run, per request
This commit is contained in:
jif-oai
2026-05-11 14:39:21 +02:00
committed by GitHub
Unverified
parent ebd3d53451
commit 7e15e6db9e
+50 -2
View File
@@ -72,8 +72,56 @@ pub trait ToolExecutor<C>: Send + Sync {
/// Returns whether the call may mutate user state.
///
/// Hosts can use this conservative signal for serialization or approval
/// policy. Context-free read tools should keep the default.
/// policy. Read-only tools should override this default.
fn is_mutating<'a>(&'a self, _call: &'a ToolCall<C>) -> BoolFuture<'a> {
Box::pin(async { false })
Box::pin(async { true })
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::Wake;
use std::task::Waker;
use super::*;
use crate::JsonToolOutput;
use crate::ToolInput;
struct DefaultMutatingExecutor;
impl ToolExecutor<()> for DefaultMutatingExecutor {
fn execute<'a>(&'a self, _call: ToolCall<()>) -> ToolFuture<'a> {
Box::pin(async {
Ok(Box::new(JsonToolOutput::new(serde_json::json!(null))) as Box<dyn ToolOutput>)
})
}
}
struct NoopWaker;
impl Wake for NoopWaker {
fn wake(self: Arc<Self>) {}
}
#[test]
fn contributed_tools_default_to_mutating() {
let call = ToolCall {
context: (),
call_id: "call-default-mutating".to_string(),
input: ToolInput::Function {
arguments: "{}".to_string(),
},
};
let mut future = DefaultMutatingExecutor.is_mutating(&call);
let waker = Waker::from(Arc::new(NoopWaker));
let mut context = Context::from_waker(&waker);
assert!(matches!(
future.as_mut().poll(&mut context),
Poll::Ready(true)
));
}
}