Skip to content

fix(sessions): recover failed resumed Session writes on a renewed interruption - #4650

Merged
seratch merged 1 commit into
openai:mainfrom
ayaangazali:fix/renewed-interruption-session-recovery
Aug 25, 2026
Merged

fix(sessions): recover failed resumed Session writes on a renewed interruption#4650
seratch merged 1 commit into
openai:mainfrom
ayaangazali:fix/renewed-interruption-session-recovery

Conversation

@ayaangazali

Copy link
Copy Markdown
Contributor

Summary

Resuming a turn that resolves one approval while another stays pending advances the run to NextStepInterruption. Both halves of the pending-write recovery added in #4630 are gated on NextStepRunAgain:

  • save_resumed_turn_items() only passes resumed_write_state when the step is NextStepRunAgain, so this transition records no pending write.
  • RunState deserialization rejects a restored pending write unless the step is NextStepRunAgain.

So when the resumed Session append raises on that transition, the failed batch has no recovery marker. The approved tool already ran and its guardrails and hooks already completed, but the function_call_output never reaches the durable Session, while RunState and RunResult.to_input_list() both keep it. Resolving the second approval then continues into another model call on top of a Session that holds a function_call with no matching output, and a later fresh run prunes the orphan, so the completed action disappears from future context.

This is the same durable divergence #4630 fixed for NextStepRunAgain, on a transition that PR did not cover. Its description scopes out handoff and terminal recovery, not this one.

Measured on an eight row matrix (sync and streamed, live and JSON round trip, atomic failure and commit-then-raise), comparing the durable Session against the replay:

before   session_ok 4/8   the four atomic-failure rows store function_call with no output
after    session_ok 8/8

The change widens both gates to accept NextStepInterruption so the existing pending-write mechanism covers the transition. No new machinery, no schema change: pending_session_write already carries everything recovery needs, and resume_pending_session_write() already reconciles it before the next model call.

Scope note: #4646 also reports that the completed tool's input/output guardrail results are dropped from RunState and every later result on this path. That is a separate ordering problem in run.py, where the guardrail results are published after the append that raises, and it is deliberately not touched here.

Test plan

tests/test_run_impl_resume_paths.py::test_renewed_interruption_recovers_failed_resumed_session_append, parametrized over streamed and non streamed and over a live state and a JSON round trip. It pauses on two approval-gated calls in one response, approves only the first, fails the resumed append, then resolves the second approval and asserts the durable Session and the replay agree.

Verified it fails without the source change by reverting src/ and rerunning: all four cases fail with ['function_call'] != ['function_call', 'function_call_output'].

.agents/skills/code-change-verification/scripts/run.sh passes end to end: format, lint, typecheck and the full test suite, including the existing #4630 coverage.

Issue number

Partially addresses #4646

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

I'm a freshman in college learning this codebase by chasing real bugs in it, so please double check my reasoning on the transaction boundary here. I picked the smallest change that made the durable state agree with the replay rather than proposing a new contract, since the pending-write mechanism already existed and only the gate looked too narrow. Happy to fold in the guardrail-result half or drop this entirely if you'd rather handle the whole transition in one go.

…erruption

A resumed turn that resolves one approval but leaves another pending advances
to NextStepInterruption. save_resumed_turn_items() and the RunState pending
write validator both gated recovery on NextStepRunAgain, so that transition
recorded no pending write. A failed append was dropped and the completed tool
output never reached the durable Session, while RunState kept it.

Widen both gates to accept NextStepInterruption so the existing pending write
mechanism covers the transition.
Copilot AI lite review requested due to automatic review settings August 25, 2026 04:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@seratch seratch added this to the 0.22.x milestone Aug 25, 2026
@seratch
seratch merged commit a624e17 into openai:main Aug 25, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants