Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions codex-rs/core/src/compact_remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,9 @@ pub(crate) async fn process_compacted_history(
///
/// This intentionally keeps:
/// - `assistant` messages (future remote compaction models may emit them)
/// - `user`-role warnings and compaction-generated summary messages because
/// they parse as `TurnItem::UserMessage`.
/// - `user`-role warnings that parse as `TurnItem::UserMessage` and compaction-generated summary
/// messages. Legacy warning fragments are filtered by `parse_turn_item` before they reach this
/// check.
fn should_keep_compacted_history_item(item: &ResponseItem) -> bool {
match item {
ResponseItem::Message { role, .. } if role == "developer" => false,
Expand Down
56 changes: 56 additions & 0 deletions codex-rs/core/src/compact_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ async fn process_compacted_history_with_test_session(
(refreshed, initial_context)
}

fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: text.to_string(),
}],
phase: None,
}
}

#[test]
fn content_items_to_text_joins_non_empty_segments() {
let items = vec![
Expand Down Expand Up @@ -120,6 +131,26 @@ do things
assert_eq!(vec!["real user message".to_string()], collected);
}

#[test]
fn collect_user_messages_filters_legacy_warnings() {
let items = vec![
user_message(
"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",
),
user_message(
"Warning: apply_patch was requested via exec_command. Use the apply_patch tool instead of exec_command.",
),
user_message(
"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",
),
user_message("real user message"),
];

let collected = collect_user_messages(&items);

assert_eq!(vec!["real user message".to_string()], collected);
}

#[test]
fn build_token_limited_compacted_history_truncates_overlong_user_messages() {
// Use a small truncation limit so the test remains fast while still validating
Expand Down Expand Up @@ -351,6 +382,31 @@ keep me updated
assert_eq!(refreshed, expected);
}

#[tokio::test]
async fn process_compacted_history_drops_legacy_warnings() {
let latest_user = user_message("latest user");
let compacted_history = vec![
user_message(
"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",
),
user_message(
"Warning: apply_patch was requested via exec_command. Use the apply_patch tool instead of exec_command.",
),
user_message(
"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",
),
latest_user.clone(),
];
let (refreshed, initial_context) = process_compacted_history_with_test_session(
compacted_history,
/*previous_turn_settings*/ None,
)
.await;
let mut expected = initial_context;
expected.push(latest_user);
assert_eq!(refreshed, expected);
}

#[tokio::test]
async fn process_compacted_history_inserts_context_before_last_real_user_message_only() {
let compacted_history = vec![
Expand Down
15 changes: 15 additions & 0 deletions codex-rs/core/src/context/contextual_user_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use super::EnvironmentContext;
use super::FragmentRegistration;
use super::FragmentRegistrationProxy;
use super::GoalContext;
use super::LegacyApplyPatchExecCommandWarning;
use super::LegacyModelMismatchWarning;
use super::LegacyUnifiedExecProcessLimitWarning;
use super::SkillInstructions;
use super::SubagentNotification;
use super::TurnAborted;
Expand All @@ -26,6 +29,15 @@ static SUBAGENT_NOTIFICATION_REGISTRATION: FragmentRegistrationProxy<SubagentNot
FragmentRegistrationProxy::new();
static GOAL_CONTEXT_REGISTRATION: FragmentRegistrationProxy<GoalContext> =
FragmentRegistrationProxy::new();
static LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION: FragmentRegistrationProxy<
LegacyUnifiedExecProcessLimitWarning,
> = FragmentRegistrationProxy::new();
static LEGACY_APPLY_PATCH_EXEC_COMMAND_WARNING_REGISTRATION: FragmentRegistrationProxy<
LegacyApplyPatchExecCommandWarning,
> = FragmentRegistrationProxy::new();
static LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION: FragmentRegistrationProxy<
LegacyModelMismatchWarning,
> = FragmentRegistrationProxy::new();

static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
&USER_INSTRUCTIONS_REGISTRATION,
Expand All @@ -35,6 +47,9 @@ static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
&TURN_ABORTED_REGISTRATION,
&SUBAGENT_NOTIFICATION_REGISTRATION,
&GOAL_CONTEXT_REGISTRATION,
&LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION,
&LEGACY_APPLY_PATCH_EXEC_COMMAND_WARNING_REGISTRATION,
Comment thread
pakrym-oai marked this conversation as resolved.
&LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION,
];

fn is_standard_contextual_user_text(text: &str) -> bool {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::ContextualUserFragment;

// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyApplyPatchExecCommandWarning;

impl ContextualUserFragment for LegacyApplyPatchExecCommandWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";

fn matches_text(text: &str) -> bool {
let trimmed = text.trim();
trimmed.starts_with("Warning: apply_patch was requested via ")
&& trimmed.ends_with("Use the apply_patch tool instead of exec_command.")
}

fn body(&self) -> String {
String::new()
}
}
21 changes: 21 additions & 0 deletions codex-rs/core/src/context/legacy_model_mismatch_warning.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::ContextualUserFragment;

// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyModelMismatchWarning;

impl ContextualUserFragment for LegacyModelMismatchWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";

fn matches_text(text: &str) -> bool {
text.trim().starts_with(
"Warning: Your account was flagged for potentially high-risk cyber activity",
)
}

fn body(&self) -> String {
String::new()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::ContextualUserFragment;

// This warning is not produced anymore but fragment definition is used to filter messaged from old sessions
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct LegacyUnifiedExecProcessLimitWarning;

impl ContextualUserFragment for LegacyUnifiedExecProcessLimitWarning {
const ROLE: &'static str = "user";
const START_MARKER: &'static str = "";
const END_MARKER: &'static str = "";

fn matches_text(text: &str) -> bool {
text.trim().starts_with(
"Warning: The maximum number of unified exec processes you can keep open is",
)
}

fn body(&self) -> String {
String::new()
}
}
6 changes: 6 additions & 0 deletions codex-rs/core/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ mod goal_context;
mod guardian_followup_review_reminder;
mod hook_additional_context;
mod image_generation_instructions;
mod legacy_apply_patch_exec_command_warning;
mod legacy_model_mismatch_warning;
mod legacy_unified_exec_process_limit_warning;
mod model_switch_instructions;
mod network_rule_saved;
mod permissions_instructions;
Expand Down Expand Up @@ -41,6 +44,9 @@ pub(crate) use goal_context::GoalContext;
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
pub(crate) use hook_additional_context::HookAdditionalContext;
pub(crate) use image_generation_instructions::ImageGenerationInstructions;
pub(crate) use legacy_apply_patch_exec_command_warning::LegacyApplyPatchExecCommandWarning;
pub(crate) use legacy_model_mismatch_warning::LegacyModelMismatchWarning;
pub(crate) use legacy_unified_exec_process_limit_warning::LegacyUnifiedExecProcessLimitWarning;
pub(crate) use model_switch_instructions::ModelSwitchInstructions;
pub(crate) use network_rule_saved::NetworkRuleSaved;
pub use permissions_instructions::PermissionsInstructions;
Expand Down
18 changes: 0 additions & 18 deletions codex-rs/core/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2436,22 +2436,6 @@ impl Session {
state.record_items(items.iter(), turn_context.truncation_policy);
}

pub(crate) async fn record_model_warning(&self, message: impl Into<String>, ctx: &TurnContext) {
self.services
.session_telemetry
.counter("codex.model_warning", /*inc*/ 1, &[]);
let item = ResponseItem::Message {
id: None,
role: "user".to_string(),
content: vec![ContentItem::InputText {
text: format!("Warning: {}", message.into()),
}],
phase: None,
};

self.record_conversation_items(ctx, &[item]).await;
}

async fn maybe_warn_on_server_model_mismatch(
self: &Arc<Self>,
turn_context: &Arc<TurnContext>,
Expand Down Expand Up @@ -2488,8 +2472,6 @@ impl Session {
}),
)
.await;
self.record_model_warning(warning_message, turn_context)
.await;
true
}

Expand Down
28 changes: 0 additions & 28 deletions codex-rs/core/src/session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5797,34 +5797,6 @@ async fn refresh_mcp_servers_is_deferred_until_next_turn() {
assert!(!new_token.is_cancelled());
}

#[tokio::test]
async fn record_model_warning_appends_user_message() {
let (mut session, turn_context) = make_session_and_context().await;
let features = Features::with_defaults().into();
session.features = features;

session
.record_model_warning("too many unified exec processes", &turn_context)
.await;

let history = session.clone_history().await;
let history_items = history.raw_items();
let last = history_items.last().expect("warning recorded");

match last {
ResponseItem::Message { role, content, .. } => {
assert_eq!(role, "user");
assert_eq!(
content,
&vec![ContentItem::InputText {
text: "Warning: too many unified exec processes".to_string(),
}]
);
}
other => panic!("expected user message, got {other:?}"),
}
}

#[tokio::test]
async fn spawn_task_does_not_update_previous_turn_settings_for_non_run_turn_tasks() {
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
Expand Down
8 changes: 0 additions & 8 deletions codex-rs/core/src/tools/handlers/apply_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,14 +490,6 @@ pub(crate) async fn intercept_apply_patch(
.await
{
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
session
.record_model_warning(
format!(
"apply_patch was requested via {tool_name}. Use the apply_patch tool instead of exec_command."
),
turn.as_ref(),
)
.await;
let (approval_keys, effective_additional_permissions, file_system_sandbox_policy) =
effective_patch_permissions(session.as_ref(), turn.as_ref(), &changes, cwd).await;
match apply_patch::apply_patch(turn.as_ref(), &file_system_sandbox_policy, changes)
Expand Down
3 changes: 0 additions & 3 deletions codex-rs/core/src/unified_exec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ pub(crate) const UNIFIED_EXEC_OUTPUT_MAX_BYTES: usize = 1024 * 1024; // 1 MiB
pub(crate) const UNIFIED_EXEC_OUTPUT_MAX_TOKENS: usize = UNIFIED_EXEC_OUTPUT_MAX_BYTES / 4;
pub(crate) const MAX_UNIFIED_EXEC_PROCESSES: usize = 64;

// Send a warning message to the models when it reaches this number of processes.
pub(crate) const WARNING_UNIFIED_EXEC_PROCESSES: usize = 60;

pub(crate) struct UnifiedExecContext {
pub session: Arc<Session>,
pub turn: Arc<TurnContext>,
Expand Down
15 changes: 2 additions & 13 deletions codex-rs/core/src/unified_exec/process_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ use crate::unified_exec::ProcessStore;
use crate::unified_exec::UnifiedExecContext;
use crate::unified_exec::UnifiedExecError;
use crate::unified_exec::UnifiedExecProcessManager;
use crate::unified_exec::WARNING_UNIFIED_EXEC_PROCESSES;
use crate::unified_exec::WriteStdinRequest;
use crate::unified_exec::async_watcher::emit_exec_end_for_unified_exec;
use crate::unified_exec::async_watcher::emit_failed_exec_end_for_unified_exec;
Expand Down Expand Up @@ -827,11 +826,11 @@ impl UnifiedExecProcessManager {
session: Arc::downgrade(&context.session),
last_used: started_at,
};
let (number_processes, pruned_entry) = {
let pruned_entry = {
let mut store = self.process_store.lock().await;
let pruned_entry = Self::prune_processes_if_needed(&mut store);
store.processes.insert(process_id, entry);
(store.processes.len(), pruned_entry)
pruned_entry
};
// prune_processes_if_needed runs while holding process_store; do async
// network-approval cleanup only after dropping that lock.
Expand All @@ -840,16 +839,6 @@ impl UnifiedExecProcessManager {
pruned_entry.process.terminate();
}

if number_processes >= WARNING_UNIFIED_EXEC_PROCESSES {
context
.session
.record_model_warning(
format!("The maximum number of unified exec processes you can keep open is {WARNING_UNIFIED_EXEC_PROCESSES} and you currently have {number_processes} processes open. Reuse older processes or close them to prevent automatic pruning of old processes"),
&context.turn
)
.await;
};

spawn_exit_watcher(
Arc::clone(&process),
Arc::clone(&context.session),
Expand Down
32 changes: 1 addition & 31 deletions codex-rs/core/tests/suite/safety_check_downgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn disabled_text_turn(test: &TestCodex, text: &str) -> Op {
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() -> Result<()> {
async fn openai_model_header_mismatch_emits_warning_event() -> Result<()> {
skip_if_no_network!(Ok(()));

let server = start_mock_server().await;
Expand Down Expand Up @@ -92,36 +92,6 @@ async fn openai_model_header_mismatch_emits_warning_event_and_warning_item() ->
assert!(warning.message.contains(REQUESTED_MODEL));
assert!(warning.message.contains(SERVER_MODEL));

let warning_item = wait_for_event(&test.codex, |event| {
matches!(
event,
EventMsg::RawResponseItem(raw)
if matches!(
&raw.item,
ResponseItem::Message { content, .. }
if content.iter().any(|item| matches!(
item,
ContentItem::InputText { text } if text.starts_with("Warning: ")
))
)
)
})
.await;
let EventMsg::RawResponseItem(raw) = warning_item else {
panic!("expected raw response item event");
};
let ResponseItem::Message { role, content, .. } = raw.item else {
panic!("expected warning to be recorded as a message item");
};
assert_eq!(role, "user");
let warning_text = content.iter().find_map(|item| match item {
ContentItem::InputText { text } => Some(text.as_str()),
_ => None,
});
let warning_text = warning_text.expect("warning message should include input_text content");
assert!(warning_text.contains(REQUESTED_MODEL));
assert!(warning_text.contains(SERVER_MODEL));

let _ = wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
})
Expand Down
Loading