Preserve incoming prompts when pre-turn compaction fails (#44487)

## Why

Pre-turn compaction runs before incoming input is recorded, so failures could leave an accepted prompt out of conversation history. Reporting the error before prompt hooks finish also lets clients steer follow-up input into a turn still preserving its prompt.

## What changed

Record input and run prompt hooks on every pre-turn compaction failure. Defer local and remote compaction error events to `run_turn`, after prompt preservation completes, while retaining the remote error prefix.

## Testing

Extend the remote compaction output-validation test to trigger automatic compaction and assert that the prompt is emitted before the error, saved exactly once, and followed by no additional model request. Retain the assertion that usage is recorded for invalid compaction output.

GitOrigin-RevId: ab7ccb259836c084173e0e81dbbd4c610e95a6c8
This commit is contained in:
jif authored and copyberry committed 2026-09-10 10:08:11 +00:00
1 parent 5d3fe48b08
commit ee93abb690
4 files changed
+85 -23

No files matched your search

+20 -8
View File
@@ -188,15 +188,16 @@ pub(crate) async fn run_turn(
)
.await
{
// Compaction runs before the new input is recorded, so preserve it on every failure.
run_hooks_and_record_inputs(
&sess,
&turn_context,
&turn_context.capture_current_model_info(),
&input,
PersistContext::Standard,
)
.await;
if matches!(err.details(), CodexErrorDetails::TurnAborted) {
run_hooks_and_record_inputs(
&sess,
&turn_context,
&turn_context.capture_current_model_info(),
&input,
PersistContext::Standard,
)
.await;
return Err(err);
}
if matches!(err.details(), CodexErrorDetails::ToolCollision(_)) {
@@ -205,6 +206,17 @@ pub(crate) async fn run_turn(
let error = err.to_codex_protocol_error();
sess.emit_turn_error_lifecycle(turn_context.as_ref(), error.clone())
.await;
// Publish the failure only after prompt hooks finish, so clients cannot react to
// an error by steering follow-up input into a turn still preserving its prompt.
let message_prefix = match turn_context.provider.capabilities().remote_compaction {
RemoteCompactionSupport::V2 => Some("Error running remote compact task".to_string()),
RemoteCompactionSupport::Unsupported => None,
};
sess.send_event(
turn_context.as_ref(),
EventMsg::Error(err.to_error_event(message_prefix)),
)
.await;
error!("Failed to run pre-sampling compact");
return Ok(None);
}