[codex] Remove auto-compaction opt-out (#29815)

## Summary

- remove the default-on `auto_compaction` feature flag and generated
config schema entries
- restore unconditional pre-turn, model-switch/hash, and mid-turn
automatic compaction
- expose `new_context` whenever token-budget tooling is enabled
- remove the disabled-auto-compaction integration coverage introduced by
#28260

## Motivation

Roll back the internal auto-compaction escape hatch added in #28260.
Automatic compaction should no longer be suppressible with `--disable
auto_compaction`; existing manual `/compact` behavior remains unchanged.

## Testing

- `just write-config-schema`
- `just test -p codex-features` — 53 passed
- `just test -p codex-core 'suite::compact::'` — 36 passed
- `just test -p codex-core
suite::token_budget::new_context_tool_starts_new_window_before_follow_up`
— 1 passed
- `just fix -p codex-core -p codex-features`
- `just fmt`
- `just test -p codex-core` — 2,778 passed, 59 failed, 16 skipped;
failures were outside the changed compaction paths and were dominated by
missing first-party test binaries and shell-snapshot timeouts
This commit is contained in:
rhan-oai
2026-06-24 00:15:04 -07:00
committed by GitHub
Unverified
parent 31e428a1ef
commit 2a320fedb5
6 changed files with 2 additions and 287 deletions
-6
View File
@@ -433,9 +433,6 @@
"auth_elicitation": {
"type": "boolean"
},
"auto_compaction": {
"type": "boolean"
},
"browser_use": {
"type": "boolean"
},
@@ -4744,9 +4741,6 @@
"auth_elicitation": {
"type": "boolean"
},
"auto_compaction": {
"type": "boolean"
},
"browser_use": {
"type": "boolean"
},
+1 -14
View File
@@ -332,13 +332,8 @@ pub(crate) async fn run_turn(
.await;
// as long as compaction works well in getting us way below the token limit, we shouldn't worry about being in an infinite loop.
let auto_compact_needed = turn_context
.config
.features
.enabled(Feature::AutoCompaction)
&& token_limit_reached;
if needs_follow_up
&& (sess.take_new_context_window_request().await || auto_compact_needed)
&& (sess.take_new_context_window_request().await || token_limit_reached)
{
if let Err(err) = run_auto_compact(
&sess,
@@ -792,14 +787,6 @@ async fn run_pre_sampling_compact(
turn_context: &Arc<TurnContext>,
client_session: &mut ModelClientSession,
) -> CodexResult<()> {
if !turn_context
.config
.features
.enabled(Feature::AutoCompaction)
{
return Ok(());
}
maybe_run_previous_model_inline_compact(sess, turn_context, client_session).await?;
let token_status =
super::context_window::context_window_token_status(sess.as_ref(), turn_context.as_ref())
+1 -3
View File
@@ -738,9 +738,7 @@ fn add_core_utility_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut
}
if features.enabled(Feature::TokenBudget) {
if features.enabled(Feature::AutoCompaction) {
planned_tools.add_with_exposure(NewContextWindowHandler, ToolExposure::DirectModelOnly);
}
planned_tools.add_with_exposure(NewContextWindowHandler, ToolExposure::DirectModelOnly);
planned_tools.add(GetContextRemainingHandler);
}
-208
View File
@@ -1,6 +1,5 @@
use anyhow::Result;
use anyhow::anyhow;
use codex_core::CodexThread;
use codex_core::compact::SUMMARIZATION_PROMPT;
use codex_core::compact::SUMMARY_PREFIX;
use codex_core::config::Config;
@@ -36,7 +35,6 @@ use core_test_support::responses;
use core_test_support::responses::ev_reasoning_item;
use core_test_support::responses::mount_models_once;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::local_selections;
use core_test_support::test_codex::test_codex;
use core_test_support::test_codex::turn_permission_fields;
@@ -94,48 +92,6 @@ const REMOTE_V2_SUMMARY: &str = "global-instructions-remote-v2-summary";
pub(super) const COMPACT_WARNING_MESSAGE: &str = "Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.";
async fn build_auto_compaction_disabled_codex(server: &MockServer) -> TestCodex {
let mut model_provider = non_openai_model_provider(server);
model_provider.stream_max_retries = Some(0);
test_codex()
.with_config(move |config| {
config.model_provider = model_provider;
set_test_compact_prompt(config);
config.model_context_window = Some(100);
config.model_auto_compact_token_limit = Some(90);
let _ = config.features.disable(Feature::AutoCompaction);
})
.build(server)
.await
.expect("build codex")
}
async fn submit_context_window_exceeded_turn(codex: &Arc<CodexThread>, text: &str) {
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: text.to_string(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: Default::default(),
})
.await
.expect("submit context window exceeded turn");
let error_message = wait_for_event_match(codex, |event| match event {
EventMsg::Error(err) => Some(err.message.clone()),
_ => None,
})
.await;
wait_for_event(codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
assert!(
error_message.contains("ran out of room in the model's context window"),
"expected context window exceeded message, got {error_message}"
);
}
fn ev_shell_command_call(call_id: &str, command: &str) -> serde_json::Value {
ev_function_call(
call_id,
@@ -2320,93 +2276,6 @@ async fn pre_sampling_compact_runs_when_comp_hash_changes() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compaction_feature_disabled_skips_comp_hash_model_switch_compaction() {
skip_if_no_network!();
let server = MockServer::start().await;
let previous_model = "gpt-5.3-codex";
let next_model = "gpt-5.2";
let models_mock = mount_models_once(
&server,
ModelsResponse {
models: vec![
model_info_with_optional_comp_hash(previous_model, Some("hash-a")),
model_info_with_optional_comp_hash(next_model, Some("hash-b")),
],
},
)
.await;
let request_log = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_assistant_message("m1", "before switch"),
ev_completed_with_tokens("r1", /*total_tokens*/ 100),
]),
sse(vec![
ev_assistant_message("m2", "after switch"),
ev_completed_with_tokens("r2", /*total_tokens*/ 100),
]),
],
)
.await;
let model_provider = non_openai_model_provider(&server);
let mut builder = test_codex()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_model(previous_model)
.with_config(move |config| {
config.model_provider = model_provider;
set_test_compact_prompt(config);
let _ = config.features.disable(Feature::AutoCompaction);
});
let test = builder.build(&server).await.expect("build test codex");
test.codex
.submit(disabled_permission_user_turn(
"before switch",
test.cwd.path().to_path_buf(),
previous_model.to_string(),
))
.await
.expect("submit first user turn");
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
test.codex
.submit(disabled_permission_user_turn(
"after switch",
test.cwd.path().to_path_buf(),
next_model.to_string(),
))
.await
.expect("submit second user turn");
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
.await;
let requests = request_log.requests();
assert_eq!(models_mock.requests().len(), 1);
assert_eq!(
requests.len(),
2,
"disabled auto-compaction should skip compaction on a comp-hash model switch"
);
let first = requests[0].body_json();
let second = requests[1].body_json();
assert_eq!(first["model"].as_str(), Some(previous_model));
assert_eq!(second["model"].as_str(), Some(next_model));
assert!(second.to_string().contains("before switch"));
assert!(second.to_string().contains("after switch"));
assert!(
!body_contains_text(&second.to_string(), SUMMARIZATION_PROMPT),
"disabled auto-compaction should preserve history instead of requesting a summary"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn pre_sampling_compact_skips_when_either_comp_hash_is_missing() {
skip_if_no_network!();
@@ -3786,45 +3655,6 @@ async fn snapshot_request_shape_mid_turn_continuation_compaction() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compaction_feature_disabled_skips_mid_turn_compaction() {
skip_if_no_network!();
let server = start_mock_server().await;
let over_limit_tokens = 100 * 95 / 100 + 1;
let first_turn = sse(vec![
ev_function_call(DUMMY_CALL_ID, DUMMY_FUNCTION_NAME, "{}"),
ev_completed_with_tokens("r1", over_limit_tokens),
]);
let request_log = mount_sse_sequence(
&server,
vec![
first_turn,
sse_failed(
"response-failed",
"context_length_exceeded",
CONTEXT_LIMIT_MESSAGE,
),
],
)
.await;
let test = build_auto_compaction_disabled_codex(&server).await;
submit_context_window_exceeded_turn(&test.codex, FUNCTION_CALL_LIMIT_MSG).await;
let requests = request_log.requests();
assert_eq!(requests.len(), 2);
let continuation_request = &requests[1];
continuation_request.function_call_output(DUMMY_CALL_ID);
assert!(
!body_contains_text(
&continuation_request.body_json().to_string(),
SUMMARIZATION_PROMPT
),
"disabled auto-compaction should continue without a compaction request"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compact_clamps_config_limit_to_context_window() {
skip_if_no_network!();
@@ -4658,44 +4488,6 @@ async fn snapshot_request_shape_pre_turn_compaction_context_window_exceeded() {
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compaction_feature_disabled_skips_pre_turn_compaction() {
skip_if_no_network!();
let server = start_mock_server().await;
let first_turn = sse(vec![
ev_assistant_message("m1", FIRST_REPLY),
ev_completed_with_tokens("r1", /*total_tokens*/ 500),
]);
let request_log = mount_sse_sequence(
&server,
vec![
first_turn,
sse_failed(
"response-failed",
"context_length_exceeded",
CONTEXT_LIMIT_MESSAGE,
),
],
)
.await;
let test = build_auto_compaction_disabled_codex(&server).await;
test.submit_turn("USER_ONE")
.await
.expect("submit first turn");
submit_context_window_exceeded_turn(&test.codex, "USER_TWO").await;
let requests = request_log.requests();
assert_eq!(requests.len(), 2);
let second_request_body = requests[1].body_json().to_string();
assert!(second_request_body.contains("USER_TWO"));
assert!(
!body_contains_text(&second_request_body, SUMMARIZATION_PROMPT),
"disabled auto-compaction should sample without a pre-turn compaction request"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn snapshot_request_shape_manual_compact_without_previous_user_messages() {
skip_if_no_network!();
-48
View File
@@ -903,51 +903,3 @@ async fn new_context_tool_starts_new_window_before_follow_up() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn auto_compaction_feature_disabled_hides_new_context_tool() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let responses = mount_sse_sequence(
&server,
vec![sse(vec![
ev_response_created("resp-1"),
ev_completed("resp-1"),
])],
)
.await;
let test = test_codex()
.with_config(|config| {
config.model_context_window = Some(CONFIGURED_CONTEXT_WINDOW);
config
.features
.enable(Feature::TokenBudget)
.expect("test config should allow token budget");
config
.features
.disable(Feature::AutoCompaction)
.expect("test config should allow disabling auto-compaction");
})
.build(&server)
.await?;
test.submit_turn("preserve the current context window")
.await?;
let requests = responses.requests();
assert_eq!(requests.len(), 1);
let tool_names = tool_names(&requests[0]);
assert!(
tool_names
.iter()
.any(|name| name == "get_context_remaining"),
"token budget should continue to expose get_context_remaining"
);
assert!(
!tool_names.iter().any(|name| name == "new_context"),
"disabled auto-compaction should hide new_context"
);
Ok(())
}
-8
View File
@@ -235,8 +235,6 @@ pub enum Feature {
RealtimeConversation,
/// Prevent idle system sleep while a turn is actively running.
PreventIdleSleep,
/// Enable automatic context compaction before or during a turn.
AutoCompaction,
/// Enable remote compaction v2 over the normal Responses API.
RemoteCompactionV2,
/// Use Agent Identity for ChatGPT-authenticated sessions.
@@ -1347,12 +1345,6 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Removed,
default_enabled: false,
},
FeatureSpec {
id: Feature::AutoCompaction,
key: "auto_compaction",
stage: Stage::Stable,
default_enabled: true,
},
FeatureSpec {
id: Feature::RemoteCompactionV2,
key: "remote_compaction_v2",