-
Notifications
You must be signed in to change notification settings - Fork 6.9k
Refresh models on 412 (etag mismatch) #8304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
codex/codex-rs/core/src/codex.rs
Lines 2412 to 2421 in e01610f
| let model_supports_parallel = turn_context | |
| .client | |
| .get_model_family() | |
| .supports_parallel_tool_calls; | |
| let prompt = Prompt { | |
| input, | |
| tools: router.specs(), | |
| parallel_tool_calls: model_supports_parallel && sess.enabled(Feature::ParallelToolCalls), | |
| base_instructions_override: turn_context.base_instructions.clone(), |
When an outdated models error occurs, run_turn refreshes the models list and calls turn_context.client.update_model_family(...), but the Prompt is built once before the retry loop with parallel_tool_calls derived from the old model family. Subsequent retries reuse the stale prompt, so if the refreshed model family changes supports_parallel_tool_calls (or other prompt-derived capabilities), the retried request can still send the wrong parallel_tool_calls value. This can cause the retry to fail again (e.g., if the model now disallows parallel tool calls) or miss newly supported behavior. Consider rebuilding the prompt (or at least recomputing parallel_tool_calls) after updating the model family.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
codex-rs/core/src/codex.rs
Outdated
| turn_context.client.update_model_family(model_family); | ||
| turn_context.client.update_models_etag(models_etag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebuild tools config after model_family refresh
When a 412 triggers OutdatedModels, you update the ModelClient’s model_family/etag and then retry, but the turn’s ToolsConfig/ToolRouter were built once before the loop from the old model_family. If the refreshed models list changes capabilities (e.g., shell_type, apply_patch_tool_type, or experimental_supported_tools), the retry still advertises stale tool definitions, which can keep failing or omit newly required tools. Consider rebuilding the tools config/router (or storing them in a lock like model_family) when the model family is refreshed.
Useful? React with 👍 / 👎.
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
codex-rs/core/src/codex.rs
Outdated
| *turn_context = sess | ||
| .new_default_turn_with_sub_id(turn_context.sub_id.clone()) | ||
| .await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve per-turn output schema on 412 retry
When a 412 triggers the OutdatedModels retry, the code rebuilds the turn context via new_default_turn_with_sub_id, which reuses the session configuration but drops per-turn overrides like final_output_json_schema (only applied in new_turn_from_configuration when Op::UserTurn supplies it). If a caller requested a structured JSON response, the retry will run without the schema and can return unstructured output, breaking clients that depend on the schema. This only occurs for turns that set final_output_json_schema and then hit the 412 path.
Useful? React with 👍 / 👎.
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
codex-rs/core/src/codex.rs
Outdated
| if let Some(final_output_json_schema) = updates.final_output_json_schema.clone() { | ||
| next_configuration.final_output_json_schema = final_output_json_schema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid persisting per-turn output schema
Persisting final_output_json_schema in SessionConfiguration makes the schema stick across future turns instead of being scoped to a single UserTurn. Since Op::UserInput and subsequent UserTurns don’t clear it unless the caller explicitly sends a new value, later turns can be unintentionally constrained by an old schema, causing unexpected validation failures or malformed outputs. This is a regression from the previous per-turn override behavior where the schema was applied only for the specific turn.
Useful? React with 👍 / 👎.
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
codex-rs/core/src/codex.rs
Outdated
| let session_configuration = sess.state.lock().await.session_configuration.clone(); | ||
| *turn_context = sess | ||
| .new_turn_from_configuration( | ||
| turn_context.sub_id.clone(), | ||
| session_configuration, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve per-turn settings when rebuilding context
When a 412 triggers refresh_models_and_reset_turn_context, the code rebuilds the turn context from session_configuration instead of the existing TurnContext. This drops per-turn overrides (e.g., review threads set base_instructions to REVIEW_PROMPT and disable WebSearch/ViewImage in spawn_review_thread), so the retried request can run with the wrong instructions/tools after an outdated-models error. That changes behavior mid-turn and can invalidate review outputs or re-enable tools that were intentionally disabled for the turn.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let models_etag = sess.services.models_manager.get_models_etag().await; | ||
| turn_context.client.update_model_family(model_family).await; | ||
| turn_context.client.update_models_etag(models_etag).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebuild tool config after models refresh
After refreshing models on a 412, you only update the client’s model family and ETag, but TurnContext.tools_config is still the one built before the refresh. ToolsConfig::new depends on the model family (e.g., shell_type, apply_patch_tool_type, experimental_supported_tools), so if the catalog change altered tool capabilities, the retry will keep advertising the old tool set and can continue to fail or send unsupported tools. Consider rebuilding tools_config (or a new turn context) alongside the refreshed model family/etag so retries reflect the updated model metadata.
Useful? React with 👍 / 👎.
|
@codex review this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review this |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
| status, | ||
| request_id: extract_request_id(headers.as_ref()), | ||
| }) | ||
| } else if status == http::StatusCode::PRECONDITION_FAILED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use a status code, don't match on strings
| model_family: RwLock<ModelFamily>, | ||
| models_etag: RwLock<Option<String>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two values are connected and should be only used/updated together. Let's not spread them across two fields.
| self.model_family.read().await.clone() | ||
| } | ||
|
|
||
| pub async fn get_models_etag(&self) -> Option<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't make the client event larger model storage God-object.
| } | ||
|
|
||
| impl Prompt { | ||
| pub(crate) async fn new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need this extracted?
This is one way to create the prompt but not the only one.
etagon clientTurnContextrefresh_available_modelsto always refresh without checking cache. Only used intry_refresh_available_modelsand refreshing mid sessionetagto responses apietagfromModelResponsesbecause we send it as a header