mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Add opt-in nonfatal handling for clock read failures (#45825)
## Why Clock provider failures can abort a turn while preparing time context or running clock tools. Allow turns to continue with an explicit indication that the current time is unavailable. ## What changed - Add `features.nonfatal_clock_read_errors`, disabled by default, to report clock failures to the model without failing the turn. - Emit a generic `failed to read current time` notice for context reads and tool errors, without exposing provider error details. Deduplicate context notices per turn and compaction window, and remove inherited notices from forked subagent context. - Omit unavailable environment dates and explicitly clear previously visible dates with `<current_date status="unavailable" />`. - Return external sleep clock failures to the model when the feature is enabled, preserving sleep item completion notifications. ## Testing Add coverage for continued inference after clock failures, notice deduplication across compaction, subagent notice filtering, environment date removal and recovery, and sleep failures during initial and polling reads. GitOrigin-RevId: a39c3723c06a6f786d8ad59d667e8b9f626c973e
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::CurrentTimeReminder;
|
||||
use crate::context::CurrentTimeUnavailable;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -8,6 +9,7 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::tools::context::boxed_tool_output;
|
||||
use crate::tools::registry::CoreToolRuntime;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
@@ -98,7 +100,21 @@ impl ToolExecutor<ToolInvocation> for CurrentTimeHandler {
|
||||
.current_time(invocation.session.thread_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
FunctionCallError::Fatal(format!("failed to read current time: {err:#}"))
|
||||
if invocation
|
||||
.turn
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::NonfatalClockReadErrors)
|
||||
{
|
||||
tracing::error!(
|
||||
thread_id = %invocation.session.thread_id,
|
||||
turn_id = %invocation.turn.sub_id,
|
||||
"failed to read current time for the clock tool; the clock provider may be stalled"
|
||||
);
|
||||
FunctionCallError::RespondToModel(CurrentTimeUnavailable::MESSAGE.to_string())
|
||||
} else {
|
||||
FunctionCallError::Fatal(format!("failed to read current time: {err:#}"))
|
||||
}
|
||||
})?;
|
||||
Ok(boxed_tool_output(CurrentTimeOutput(
|
||||
CurrentTimeReminder::new(current_time),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::context::CurrentTimeUnavailable;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -8,6 +9,7 @@ use crate::tools::registry::CoreToolRuntime;
|
||||
use crate::tools::registry::ToolExecutor;
|
||||
use codex_extension_items::ExtensionItem;
|
||||
use codex_extension_items::sleep::SleepItem;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_tools::JsonSchema;
|
||||
use codex_tools::ResponsesApiNamespace;
|
||||
@@ -111,7 +113,7 @@ impl ToolExecutor<ToolInvocation> for SleepHandler {
|
||||
.input_queue
|
||||
.subscribe_activity(turn_state.as_deref())
|
||||
.await;
|
||||
let sleep_result: Result<bool, FunctionCallError> = if pending_activity.is_some() {
|
||||
let sleep_result = if pending_activity.is_some() {
|
||||
Ok(true)
|
||||
} else {
|
||||
let sleep = session
|
||||
@@ -120,27 +122,29 @@ impl ToolExecutor<ToolInvocation> for SleepHandler {
|
||||
.sleep(session.thread_id, Duration::from_millis(args.duration_ms));
|
||||
tokio::pin!(sleep);
|
||||
tokio::select! {
|
||||
result = &mut sleep => result
|
||||
.map(|()| false)
|
||||
.map_err(|err| {
|
||||
FunctionCallError::Fatal(format!("failed to sleep: {err:#}"))
|
||||
}),
|
||||
result = &mut sleep => result.map(|()| false),
|
||||
result = activity_rx.changed() => {
|
||||
if result.is_ok() {
|
||||
Ok(true)
|
||||
} else {
|
||||
sleep
|
||||
.await
|
||||
.map(|()| false)
|
||||
.map_err(|err| {
|
||||
FunctionCallError::Fatal(format!("failed to sleep: {err:#}"))
|
||||
})
|
||||
sleep.await.map(|()| false)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
session.emit_turn_item_completed(turn.as_ref(), item).await;
|
||||
let interrupted = sleep_result?;
|
||||
let interrupted = sleep_result.map_err(|err| {
|
||||
if turn.config.features.enabled(Feature::NonfatalClockReadErrors) {
|
||||
tracing::error!(
|
||||
thread_id = %session.thread_id,
|
||||
turn_id = %turn.sub_id,
|
||||
"failed to read current time for the sleep tool; the clock provider may be stalled"
|
||||
);
|
||||
FunctionCallError::RespondToModel(CurrentTimeUnavailable::MESSAGE.to_string())
|
||||
} else {
|
||||
FunctionCallError::Fatal(format!("failed to sleep: {err:#}"))
|
||||
}
|
||||
})?;
|
||||
|
||||
let message = if interrupted {
|
||||
"Sleep interrupted by new input."
|
||||
|
||||
Reference in New Issue
Block a user