Skip to content

Commit 79c65f8

Browse files
authored
[codex] Filter legacy warning messages during compaction (openai#22243)
## Why Older sessions can contain model-warning records persisted as `user` messages, including the unified exec process-limit warning, the `apply_patch`-via-`exec_command` warning, and the model-mismatch high-risk cyber fallback warning. Those warnings are no longer produced as conversation history items, but when old sessions compact they should still be recognized as injected context rather than preserved as real user turns. ## What changed - Removed `record_model_warning` and the production paths that emitted these warning messages into conversation history. - Added `LegacyUnifiedExecProcessLimitWarning`, `LegacyApplyPatchExecCommandWarning`, and `LegacyModelMismatchWarning` contextual fragments that are used only for matching old persisted messages. - Registered the legacy fragments with contextual user message detection so compaction filters them through the existing fragment path. - Added focused compaction coverage for old warning messages being dropped during compacted-history processing. ## Testing - `cargo test -p codex-core warning` - `just fix -p codex-core`
1 parent d08906a commit 79c65f8

13 files changed

Lines changed: 146 additions & 103 deletions

codex-rs/core/src/compact_remote.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,9 @@ pub(crate) async fn process_compacted_history(
287287
///
288288
/// This intentionally keeps:
289289
/// - `assistant` messages (future remote compaction models may emit them)
290-
/// - `user`-role warnings and compaction-generated summary messages because
291-
/// they parse as `TurnItem::UserMessage`.
290+
/// - `user`-role warnings that parse as `TurnItem::UserMessage` and compaction-generated summary
291+
/// messages. Legacy warning fragments are filtered by `parse_turn_item` before they reach this
292+
/// check.
292293
fn should_keep_compacted_history_item(item: &ResponseItem) -> bool {
293294
match item {
294295
ResponseItem::Message { role, .. } if role == "developer" => false,

codex-rs/core/src/compact_tests.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ async fn process_compacted_history_with_test_session(
2323
(refreshed, initial_context)
2424
}
2525

26+
fn user_message(text: &str) -> ResponseItem {
27+
ResponseItem::Message {
28+
id: None,
29+
role: "user".to_string(),
30+
content: vec![ContentItem::InputText {
31+
text: text.to_string(),
32+
}],
33+
phase: None,
34+
}
35+
}
36+
2637
#[test]
2738
fn content_items_to_text_joins_non_empty_segments() {
2839
let items = vec![
@@ -120,6 +131,26 @@ do things
120131
assert_eq!(vec!["real user message".to_string()], collected);
121132
}
122133

134+
#[test]
135+
fn collect_user_messages_filters_legacy_warnings() {
136+
let items = vec![
137+
user_message(
138+
"Warning: The maximum number of unified exec processes you can keep open is 60 and you currently have 61 processes open. Reuse older processes or close them to prevent automatic pruning of old processes",
139+
),
140+
user_message(
141+
"Warning: apply_patch was requested via exec_command. Use the apply_patch tool instead of exec_command.",
142+
),
143+
user_message(
144+
"Warning: Your account was flagged for potentially high-risk cyber activity and this request was routed to gpt-5.2 as a fallback. To regain access to gpt-5.3-codex, apply for trusted access: https://chatgpt.com/cyber or learn more: https://developers.openai.com/codex/concepts/cyber-safety",
145+
),
146+
user_message("real user message"),
147+
];
148+
149+
let collected = collect_user_messages(&items);
150+
151+
assert_eq!(vec!["real user message".to_string()], collected);
152+
}
153+
123154
#[test]
124155
fn build_token_limited_compacted_history_truncates_overlong_user_messages() {
125156
// Use a small truncation limit so the test remains fast while still validating
@@ -351,6 +382,31 @@ keep me updated
351382
assert_eq!(refreshed, expected);
352383
}
353384

385+
#[tokio::test]
386+
async fn process_compacted_history_drops_legacy_warnings() {
387+
let latest_user = user_message("latest user");
388+
let compacted_history = vec![
389+
user_message(
390+
"Warning: The maximum number of unified exec processes you can keep open is 60 and you currently have 61 processes open. Reuse older processes or close them to prevent automatic pruning of old processes",
391+
),
392+
user_message(
393+
"Warning: apply_patch was requested via exec_command. Use the apply_patch tool instead of exec_command.",
394+
),
395+
user_message(
396+
"Warning: Your account was flagged for potentially high-risk cyber activity and this request was routed to gpt-5.2 as a fallback. To regain access to gpt-5.3-codex, apply for trusted access: https://chatgpt.com/cyber or learn more: https://developers.openai.com/codex/concepts/cyber-safety",
397+
),
398+
latest_user.clone(),
399+
];
400+
let (refreshed, initial_context) = process_compacted_history_with_test_session(
401+
compacted_history,
402+
/*previous_turn_settings*/ None,
403+
)
404+
.await;
405+
let mut expected = initial_context;
406+
expected.push(latest_user);
407+
assert_eq!(refreshed, expected);
408+
}
409+
354410
#[tokio::test]
355411
async fn process_compacted_history_inserts_context_before_last_real_user_message_only() {
356412
let compacted_history = vec![

codex-rs/core/src/context/contextual_user_message.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use super::EnvironmentContext;
66
use super::FragmentRegistration;
77
use super::FragmentRegistrationProxy;
88
use super::GoalContext;
9+
use super::LegacyApplyPatchExecCommandWarning;
10+
use super::LegacyModelMismatchWarning;
11+
use super::LegacyUnifiedExecProcessLimitWarning;
912
use super::SkillInstructions;
1013
use super::SubagentNotification;
1114
use super::TurnAborted;
@@ -26,6 +29,15 @@ static SUBAGENT_NOTIFICATION_REGISTRATION: FragmentRegistrationProxy<SubagentNot
2629
FragmentRegistrationProxy::new();
2730
static GOAL_CONTEXT_REGISTRATION: FragmentRegistrationProxy<GoalContext> =
2831
FragmentRegistrationProxy::new();
32+
static LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION: FragmentRegistrationProxy<
33+
LegacyUnifiedExecProcessLimitWarning,
34+
> = FragmentRegistrationProxy::new();
35+
static LEGACY_APPLY_PATCH_EXEC_COMMAND_WARNING_REGISTRATION: FragmentRegistrationProxy<
36+
LegacyApplyPatchExecCommandWarning,
37+
> = FragmentRegistrationProxy::new();
38+
static LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION: FragmentRegistrationProxy<
39+
LegacyModelMismatchWarning,
40+
> = FragmentRegistrationProxy::new();
2941

3042
static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
3143
&USER_INSTRUCTIONS_REGISTRATION,
@@ -35,6 +47,9 @@ static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
3547
&TURN_ABORTED_REGISTRATION,
3648
&SUBAGENT_NOTIFICATION_REGISTRATION,
3749
&GOAL_CONTEXT_REGISTRATION,
50+
&LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION,
51+
&LEGACY_APPLY_PATCH_EXEC_COMMAND_WARNING_REGISTRATION,
52+
&LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION,
3853
];
3954

4055
fn is_standard_contextual_user_text(text: &str) -> bool {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::ContextualUserFragment;
2+
3+
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub(crate) struct LegacyApplyPatchExecCommandWarning;
6+
7+
impl ContextualUserFragment for LegacyApplyPatchExecCommandWarning {
8+
const ROLE: &'static str = "user";
9+
const START_MARKER: &'static str = "";
10+
const END_MARKER: &'static str = "";
11+
12+
fn matches_text(text: &str) -> bool {
13+
let trimmed = text.trim();
14+
trimmed.starts_with("Warning: apply_patch was requested via ")
15+
&& trimmed.ends_with("Use the apply_patch tool instead of exec_command.")
16+
}
17+
18+
fn body(&self) -> String {
19+
String::new()
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::ContextualUserFragment;
2+
3+
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub(crate) struct LegacyModelMismatchWarning;
6+
7+
impl ContextualUserFragment for LegacyModelMismatchWarning {
8+
const ROLE: &'static str = "user";
9+
const START_MARKER: &'static str = "";
10+
const END_MARKER: &'static str = "";
11+
12+
fn matches_text(text: &str) -> bool {
13+
text.trim().starts_with(
14+
"Warning: Your account was flagged for potentially high-risk cyber activity",
15+
)
16+
}
17+
18+
fn body(&self) -> String {
19+
String::new()
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::ContextualUserFragment;
2+
3+
// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
4+
#[derive(Debug, Clone, PartialEq)]
5+
pub(crate) struct LegacyUnifiedExecProcessLimitWarning;
6+
7+
impl ContextualUserFragment for LegacyUnifiedExecProcessLimitWarning {
8+
const ROLE: &'static str = "user";
9+
const START_MARKER: &'static str = "";
10+
const END_MARKER: &'static str = "";
11+
12+
fn matches_text(text: &str) -> bool {
13+
text.trim().starts_with(
14+
"Warning: The maximum number of unified exec processes you can keep open is",
15+
)
16+
}
17+
18+
fn body(&self) -> String {
19+
String::new()
20+
}
21+
}

codex-rs/core/src/context/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ mod goal_context;
1212
mod guardian_followup_review_reminder;
1313
mod hook_additional_context;
1414
mod image_generation_instructions;
15+
mod legacy_apply_patch_exec_command_warning;
16+
mod legacy_model_mismatch_warning;
17+
mod legacy_unified_exec_process_limit_warning;
1518
mod model_switch_instructions;
1619
mod network_rule_saved;
1720
mod permissions_instructions;
@@ -41,6 +44,9 @@ pub(crate) use goal_context::GoalContext;
4144
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
4245
pub(crate) use hook_additional_context::HookAdditionalContext;
4346
pub(crate) use image_generation_instructions::ImageGenerationInstructions;
47+
pub(crate) use legacy_apply_patch_exec_command_warning::LegacyApplyPatchExecCommandWarning;
48+
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
49+
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
4450
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
4551
pub(crate) use network_rule_saved::NetworkRuleSaved;
4652
pub use permissions_instructions::PermissionsInstructions;

codex-rs/core/src/session/mod.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,22 +2436,6 @@ impl Session {
24362436
state.record_items(items.iter(), turn_context.truncation_policy);
24372437
}
24382438

2439-
pub(crate) async fn record_model_warning(&self, message: impl Into<String>, ctx: &TurnContext) {
2440-
self.services
2441-
.session_telemetry
2442-
.counter("codex.model_warning", /*inc*/ 1, &[]);
2443-
let item = ResponseItem::Message {
2444-
id: None,
2445-
role: "user".to_string(),
2446-
content: vec![ContentItem::InputText {
2447-
text: format!("Warning: {}", message.into()),
2448-
}],
2449-
phase: None,
2450-
};
2451-
2452-
self.record_conversation_items(ctx, &[item]).await;
2453-
}
2454-
24552439
async fn maybe_warn_on_server_model_mismatch(
24562440
self: &Arc<Self>,
24572441
turn_context: &Arc<TurnContext>,
@@ -2488,8 +2472,6 @@ impl Session {
24882472
}),
24892473
)
24902474
.await;
2491-
self.record_model_warning(warning_message, turn_context)
2492-
.await;
24932475
true
24942476
}
24952477

codex-rs/core/src/session/tests.rs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5797,34 +5797,6 @@ async fn refresh_mcp_servers_is_deferred_until_next_turn() {
57975797
assert!(!new_token.is_cancelled());
57985798
}
57995799

5800-
#[tokio::test]
5801-
async fn record_model_warning_appends_user_message() {
5802-
let (mut session, turn_context) = make_session_and_context().await;
5803-
let features = Features::with_defaults().into();
5804-
session.features = features;
5805-
5806-
session
5807-
.record_model_warning("too many unified exec processes", &turn_context)
5808-
.await;
5809-
5810-
let history = session.clone_history().await;
5811-
let history_items = history.raw_items();
5812-
let last = history_items.last().expect("warning recorded");
5813-
5814-
match last {
5815-
ResponseItem::Message { role, content, .. } => {
5816-
assert_eq!(role, "user");
5817-
assert_eq!(
5818-
content,
5819-
&vec![ContentItem::InputText {
5820-
text: "Warning: too many unified exec processes".to_string(),
5821-
}]
5822-
);
5823-
}
5824-
other => panic!("expected user message, got {other:?}"),
5825-
}
5826-
}
5827-
58285800
#[tokio::test]
58295801
async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() {
58305802
let (sess, tc, _rx) = make_session_and_context_with_rx().await;

codex-rs/core/src/tools/handlers/apply_patch.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -506,14 +506,6 @@ pub(crate) async fn intercept_apply_patch(
506506
.await
507507
{
508508
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
509-
session
510-
.record_model_warning(
511-
format!(
512-
"apply_patch was requested via {tool_name}. Use the apply_patch tool instead of exec_command."
513-
),
514-
turn.as_ref(),
515-
)
516-
.await;
517509
let (approval_keys, effective_additional_permissions, file_system_sandbox_policy) =
518510
effective_patch_permissions(session.as_ref(), turn.as_ref(), &changes, cwd).await;
519511
match apply_patch::apply_patch(turn.as_ref(), &file_system_sandbox_policy, changes)

0 commit comments

Comments
 (0)