mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
fix: retry rejected previous-model compaction with selected model (#30319)
## Why Pre-sampling compaction intentionally uses the previous turn's model when the compaction compatibility hash changes or when switching to a model with a smaller context window. This keeps compaction aligned with the settings that produced the history, but it can block the next turn when a resumed ChatGPT thread still references a model slug that has since been retired. The Codex backend rejects that compaction request before the user's currently selected model gets a chance to sample. This PR lets those threads recover without changing previous-model compaction behavior for API-key authentication or custom providers. It is stacked on #31316, which is a behavior-preserving extraction of the individual remote compaction attempts; this PR contains the fallback behavior. ## What changed - For automatic previous-model compaction, capture the selected model's request context when using ChatGPT authentication with the OpenAI provider and the selected model differs from the previous model. - If the previous-model attempt returns an `InvalidRequest`, retry compaction once with the selected model for both `/responses/compact` and Responses Compaction V2. - Complete history processing, lifecycle events, and token accounting with the context of the model that successfully compacted the thread. - If the fallback also fails, return the original previous-model error so the retry does not change the user-visible failure. - Record fallback attempts with reason, implementation, and outcome telemetry. - Leave API-key authentication, custom providers, same-model turns, and non-`InvalidRequest` failures on their existing paths. ## Testing - `just test -p codex-core -E 'test(pre_sampling_compact) | test(model_unavailable_error)'` (10 tests) - Added integration coverage for a resumed thread whose model was renamed, a model downshift using Responses Compaction V2, and API-key authentication with a custom provider.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
use codex_analytics::CompactionImplementation;
|
||||
use codex_analytics::CompactionReason;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) fn record_model_fallback(
|
||||
session_telemetry: &SessionTelemetry,
|
||||
previous_model: &str,
|
||||
current_model: &str,
|
||||
reason: CompactionReason,
|
||||
implementation: CompactionImplementation,
|
||||
fallback_error: Option<&CodexErr>,
|
||||
) {
|
||||
let reason_tag = match reason {
|
||||
CompactionReason::UserRequested => "user_requested",
|
||||
CompactionReason::ContextLimit => "context_limit",
|
||||
CompactionReason::ModelDownshift => "model_downshift",
|
||||
CompactionReason::CompHashChanged => "comp_hash_changed",
|
||||
};
|
||||
let implementation_tag = match implementation {
|
||||
CompactionImplementation::Responses => "responses",
|
||||
CompactionImplementation::ResponsesCompactionV2 => "responses_compaction_v2",
|
||||
CompactionImplementation::ResponsesCompact => "responses_compact",
|
||||
};
|
||||
let outcome = if fallback_error.is_none() {
|
||||
"succeeded"
|
||||
} else {
|
||||
"failed"
|
||||
};
|
||||
session_telemetry.counter(
|
||||
"codex.compaction.model_fallback",
|
||||
/*inc*/ 1,
|
||||
&[
|
||||
("reason", reason_tag),
|
||||
("implementation", implementation_tag),
|
||||
("outcome", outcome),
|
||||
],
|
||||
);
|
||||
warn!(
|
||||
previous_model,
|
||||
current_model,
|
||||
?reason,
|
||||
?implementation,
|
||||
outcome,
|
||||
?fallback_error,
|
||||
"previous-model compaction failed; retried with current model"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user