mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Improve Guardian retries and review failure reporting (#44482)
## Why Transient rate limits can end automatic approval reviews prematurely, and review failures currently report high risk even when no assessment completed. ## What changed - Retry rate limits and recoverable exhausted-stream errors, while excluding non-transient HTTP failures. - Preserve server retry delays after stream retries are exhausted and honor them within the review deadline. Scope retry advice to the current turn so reused sessions cannot apply stale delays. - Keep failed reviews denied, but leave risk and authorization unset and explain that the review could not complete without declaring the action unsafe. ## Testing Add an integration test covering rate-limit recovery through approval and tool execution, asserting that the action executes exactly once after approval. Update failure assertions to check absent assessment fields and the review-failure explanation. GitOrigin-RevId: 1163cfde35c6b8eb23b6f24f1f24461b86ded838
This commit is contained in:
@@ -398,6 +398,7 @@ async fn run_review_on_session(
|
||||
GuardianReviewSessionOutcome::SessionFailed {
|
||||
error,
|
||||
error_info: None,
|
||||
retry_at: None,
|
||||
},
|
||||
false,
|
||||
analytics_result,
|
||||
@@ -429,6 +430,7 @@ async fn run_review_on_session(
|
||||
GuardianReviewSessionOutcome::SessionFailed {
|
||||
error: error.into(),
|
||||
error_info: None,
|
||||
retry_at: None,
|
||||
},
|
||||
false,
|
||||
analytics_result,
|
||||
@@ -721,6 +723,7 @@ async fn run_review_on_session(
|
||||
GuardianReviewSessionOutcome::SessionFailed {
|
||||
error: anyhow!("guardian review input was not started: {submission:?}"),
|
||||
error_info: None,
|
||||
retry_at: None,
|
||||
},
|
||||
false,
|
||||
analytics_result,
|
||||
@@ -731,6 +734,7 @@ async fn run_review_on_session(
|
||||
GuardianReviewSessionOutcome::SessionFailed {
|
||||
error: err.into(),
|
||||
error_info: None,
|
||||
retry_at: None,
|
||||
},
|
||||
false,
|
||||
analytics_result,
|
||||
@@ -945,6 +949,10 @@ async fn wait_for_guardian_review(
|
||||
GuardianReviewSessionOutcome::SessionFailed {
|
||||
error: anyhow!(error.message),
|
||||
error_info: error.codex_error_info,
|
||||
retry_at: review_session.session.services.thread_extension_data
|
||||
.get::<crate::responses_retry::ExhaustedResponseRetry>()
|
||||
.filter(|advice| advice.turn_id == expected_turn_id)
|
||||
.and_then(|advice| advice.retry_at),
|
||||
},
|
||||
true,
|
||||
true,
|
||||
|
||||
@@ -1030,7 +1030,10 @@ async fn wait_for_guardian_review_preserves_structured_session_error() {
|
||||
)
|
||||
.await;
|
||||
|
||||
let GuardianReviewSessionOutcome::SessionFailed { error, error_info } = outcome else {
|
||||
let GuardianReviewSessionOutcome::SessionFailed {
|
||||
error, error_info, ..
|
||||
} = outcome
|
||||
else {
|
||||
panic!("expected structured session failure");
|
||||
};
|
||||
assert_eq!(error.to_string(), "temporary failure");
|
||||
|
||||
@@ -2985,7 +2985,7 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
|
||||
"denial rationale should not fall back to the generic missing payload error"
|
||||
);
|
||||
assert!(
|
||||
rejection.contains("Reason: Automatic approval review failed:")
|
||||
rejection.starts_with("Automatic approval review failed:")
|
||||
&& rejection.contains(error_message),
|
||||
"rejection message should include guardian rationale: {rejection}"
|
||||
);
|
||||
|
||||
@@ -39,6 +39,13 @@ impl Default for ResponsesStreamRetryState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Server retry advice retained after stream retries are exhausted. The turn ID
|
||||
/// prevents a reused Guardian session from applying advice from an earlier review.
|
||||
pub(crate) struct ExhaustedResponseRetry {
|
||||
pub(crate) turn_id: String,
|
||||
pub(crate) retry_at: Option<tokio::time::Instant>,
|
||||
}
|
||||
|
||||
/// Handles a retryable stream error and returns `Ok(())` when the caller should
|
||||
/// retry the request loop.
|
||||
pub(crate) async fn handle_retryable_response_stream_error(
|
||||
@@ -125,6 +132,14 @@ pub(crate) async fn handle_retryable_response_stream_error(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sess.services
|
||||
.thread_extension_data
|
||||
.insert(ExhaustedResponseRetry {
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
retry_at: err
|
||||
.retry_delay()
|
||||
.and_then(|delay| tokio::time::Instant::now().checked_add(delay)),
|
||||
});
|
||||
Err(err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user