openai / openai/codex

Custom base instructions are duplicated inside <model_switch> after switching models on a later turn

Open
#41,491 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI config
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

What issue are you seeing?

When a thread uses custom base instructions, switching models after at least one completed turn duplicates those instructions inside a developer <model_switch> message.

The same custom instructions remain present in the top-level Responses API instructions field, so the second request contains the instructions twice and under two different roles.

Observed request shape:

instructions:
custom base instructions
<model_switch>
The user was previously using a different model. Please continue the conversation according to the following instructions:

custom base instructions
</model_switch>

Environment:

openai/codex commit: 5fc7840cf6d085a7a7b3438d69a2beb934a2a5f4
platform: macOS 26.3.2 arm64 (Darwin 25.3.0)
rustc: 1.94.0

I reproduced this with a request-level codex-core integration test on the latest origin/main. The local nextest profile retried the failure once, and the duplicated custom instructions were observed 2/2 times.

What steps can reproduce the bug?
  1. Start a thread with a custom config.base_instructions value.
  2. Complete one turn using model A.
  3. Switch the thread to model B and complete another turn.
  4. Inspect the second Responses API request.
  5. Verify that the custom instructions remain in the top-level instructions field.
  6. Inspect developer input for a <model_switch> message.

I added a local integration test named:

model_change_does_not_wrap_custom_base_instructions

The essential sequence is:

let custom_base_instructions = "custom base instructions";
let mut builder = test_codex()
    .with_model("gpt-5.2")
    .with_config(move |config| {
        config.base_instructions = Some(custom_base_instructions.to_string());
    });
let test = builder.build_with_auto_env(&server).await?;

submit_model_turn(
    &test.codex,
    "gpt-5.2",
    ThreadSettingsOverrides::default(),
)
.await?;

submit_model_turn(
    &test.codex,
    "gpt-5.4",
    ThreadSettingsOverrides::default(),
)
.await?;

I ran:

just test -p codex-core model_change_does_not_wrap_custom_base_instructions

The top-level instructions assertion passes, but the assertion that no model-switch developer message exists fails with:

[
    "<model_switch>
The user was previously using a different model. Please continue the conversation according to the following instructions:

custom base instructions
</model_switch>",
]
What is the expected behavior?

Custom base instructions should remain only in the top-level Responses API instructions field when the model changes.

No <model_switch> developer message should be generated when the effective base instructions have BaseInstructionsProvenance::Custom.

Only model-derived instructions should be eligible for inclusion in <model_switch>. Existing behavior for BaseInstructionsProvenance::Model { .. } should remain unchanged so that a model change can still inject the target model's native instructions.

Additional information
Root-cause hypothesis

This is a code-path hypothesis rather than a maintainer-confirmed root cause:

This means the first-turn custom-instruction path is protected by provenance inference, but later turns bypass that protection once previous_turn_settings exists.

Possible direction

A focused direction would be to preserve model identity in the world-state snapshot while gating the model-switch instruction fragment on base-instruction provenance:

  • BaseInstructionsProvenance::Model { .. }: allow <model_switch> generation.
  • BaseInstructionsProvenance::Custom: do not generate <model_switch>.
  • Missing provenance: handle explicitly and conservatively after legacy provenance inference.

This keeps the existing model-derived behavior intact while preventing effective custom instructions from being mistaken for target-model-native instructions.

Regression coverage should include:

  • custom instructions + completed model-A turn + switch to model B → no <model_switch>;
  • custom instructions remain in top-level instructions;
  • model-derived model A → model B still injects model B's native instructions;
  • missing/legacy provenance behavior remains explicit.

I searched open and closed Issues and PRs for custom base instructions, model switching, <model_switch>, and base-instruction provenance and did not find an equivalent report.

I have traced the relevant model-resolution and world-state paths and have a deterministic request-level regression test for this behavior. If this is considered a bug and the focused direction matches the intended semantics, I would be happy to implement the fix. Would the Codex team be willing to invite me to open a PR for it?

Disclosure: this analysis was produced with significant assistance from OpenAI Codex. It is based only on the public openai/codex repository and a synthetic integration test; it contains no private code, data, prompts, roadmaps, or product-specific examples.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with codex-rs/core/src/session/world_state.rs and codex-rs/core/src/context/world_state/model.rs, then review the related configuration paths in codex-rs/core/src/config/mod.rs and codex-rs/models-manager/src/model_info.rs. Run the named model_change_does_not_wrap_custom_base_instructions integration test. Done means custom instructions remain only in top-level instructions after a model switch, while model-derived instructions retain existing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.