mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +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:
@@ -11,6 +11,7 @@ use super::turn_context::TurnContext;
|
||||
use crate::config::Config;
|
||||
use crate::config::CurrentTimeReminderConfig;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::CurrentTimeUnavailable;
|
||||
use crate::context_manager::is_user_turn_boundary;
|
||||
|
||||
pub(super) fn apply_persistent_defaults(config: &mut Config) {
|
||||
@@ -40,6 +41,7 @@ pub(super) fn apply_persistent_defaults(config: &mut Config) {
|
||||
pub(crate) struct CurrentTimeReminderState {
|
||||
last_delivery_time: Option<DateTime<Utc>>,
|
||||
last_window_id: Option<String>,
|
||||
last_clock_failure: Option<(String, uuid::Uuid)>,
|
||||
pending_user_or_tool_output_boundary: bool,
|
||||
}
|
||||
|
||||
@@ -94,6 +96,59 @@ impl CurrentTimeReminderState {
|
||||
}
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub(super) async fn read_clock_for_context(
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
clock_read: &'static str,
|
||||
) -> CodexResult<Option<DateTime<Utc>>> {
|
||||
let error = match self
|
||||
.services
|
||||
.time_provider
|
||||
.current_time(self.thread_id)
|
||||
.await
|
||||
{
|
||||
Ok(time) => return Ok(Some(time)),
|
||||
Err(error) => error,
|
||||
};
|
||||
if !turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::NonfatalClockReadErrors)
|
||||
{
|
||||
return Err(CodexErr::Fatal(format!(
|
||||
"failed to read current time: {error:#}"
|
||||
)));
|
||||
}
|
||||
tracing::error!(
|
||||
clock_read,
|
||||
thread_id = %self.thread_id,
|
||||
turn_id = %turn_context.sub_id,
|
||||
"failed to read current time; the clock provider may be stalled"
|
||||
);
|
||||
{
|
||||
let mut state = self.state.lock().await;
|
||||
let failure = (
|
||||
turn_context.sub_id.clone(),
|
||||
state.auto_compact_window_ids().window_id,
|
||||
);
|
||||
// A compacted window may no longer contain the earlier notice.
|
||||
if state.current_time_reminder.last_clock_failure.as_ref() == Some(&failure) {
|
||||
return Ok(None);
|
||||
}
|
||||
state.current_time_reminder.last_clock_failure = Some(failure);
|
||||
}
|
||||
let response_item = ContextualUserFragment::into(CurrentTimeUnavailable);
|
||||
self.record_conversation_items(
|
||||
turn_context,
|
||||
turn_context.model_info(),
|
||||
std::slice::from_ref(&response_item),
|
||||
)
|
||||
.await;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn maybe_record_current_time_reminder(
|
||||
sess: &Session,
|
||||
turn_context: &TurnContext,
|
||||
@@ -110,12 +165,12 @@ pub(super) async fn maybe_record_current_time_reminder(
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let current_time = sess
|
||||
.services
|
||||
.time_provider
|
||||
.current_time(sess.thread_id)
|
||||
.await
|
||||
.map_err(|err| CodexErr::Fatal(format!("failed to read current time: {err:#}")))?;
|
||||
let Some(current_time) = sess
|
||||
.read_clock_for_context(turn_context, "reminder")
|
||||
.await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let reminder_is_due = {
|
||||
let mut state = sess.state.lock().await;
|
||||
|
||||
@@ -25,7 +25,6 @@ use crate::context::world_state::WorldState;
|
||||
use codex_connectors::AppToolPolicyEvaluator;
|
||||
use codex_extension_api::WorldStateContributionInput;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::BaseInstructionsProvenance;
|
||||
|
||||
@@ -199,19 +198,19 @@ impl Session {
|
||||
}
|
||||
if turn_context.config.include_environment_context {
|
||||
let current_date = self
|
||||
.services
|
||||
.time_provider
|
||||
.current_time(self.thread_id())
|
||||
.await
|
||||
.map_err(|err| CodexErr::Fatal(format!("failed to read current time: {err:#}")))?
|
||||
.with_timezone(&chrono::Local)
|
||||
.format("%Y-%m-%d")
|
||||
.to_string();
|
||||
.read_clock_for_context(turn_context, "environment_date")
|
||||
.await?
|
||||
.map(|current_time| {
|
||||
current_time
|
||||
.with_timezone(&chrono::Local)
|
||||
.format("%Y-%m-%d")
|
||||
.to_string()
|
||||
});
|
||||
world_state.add_section(
|
||||
EnvironmentsState::from_turn_context_with_environments(
|
||||
turn_context,
|
||||
&step_context.environments,
|
||||
Some(current_date),
|
||||
current_date,
|
||||
)
|
||||
.await
|
||||
.with_subagents(environment_subagents),
|
||||
|
||||
Reference in New Issue
Block a user