Skip to content

fix: await cancelled output guardrail tasks on tripwire#3187

Open
Quratulain-bilal wants to merge 4 commits intoopenai:mainfrom
Quratulain-bilal:fix/await-cancelled-output-guardrails
Open

fix: await cancelled output guardrail tasks on tripwire#3187
Quratulain-bilal wants to merge 4 commits intoopenai:mainfrom
Quratulain-bilal:fix/await-cancelled-output-guardrails

Conversation

@Quratulain-bilal
Copy link
Copy Markdown
Contributor

When an output guardrail trips, run_output_guardrails cancels its sibling tasks but never awaits them, unlike the input-guardrail variants which gather the cancelled tasks. This can leak pending tasks and emit "Task was destroyed but it is pending!" warnings when slow guardrails are still mid-flight at tripwire time. Mirror the input-guardrail pattern by awaiting the cancelled tasks before raising.

When an output guardrail trips, run_output_guardrails cancels its sibling
tasks but never awaits them, unlike the input-guardrail variants which
gather the cancelled tasks. This can leak pending tasks and emit
"Task was destroyed but it is pending!" warnings when slow guardrails
are still mid-flight at tripwire time. Mirror the input-guardrail
pattern by awaiting the cancelled tasks before raising.
@github-actions github-actions Bot added bug Something isn't working feature:core labels May 8, 2026
@seratch
Copy link
Copy Markdown
Member

seratch commented May 8, 2026

@codex review

@openai openai deleted a comment from chatgpt-codex-connector Bot May 8, 2026
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

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

Please make the regression test deterministic. Right now fast_tripwire can return before slow_then_observe_cancel has reached its await asyncio.sleep(5), so the slow task may be cancelled before its CancelledError handler is installed. In that schedule, cancelled_observed stays unset even though the production fix is correct.

A better shape would be to add a slow_started = asyncio.Event(), set it at the start of slow_then_observe_cancel, and have fast_tripwire await slow_started.wait() before returning the tripwire result. That ensures the test is actually proving that an in-flight sibling guardrail is awaited after cancellation.

Use a slow_started event so fast_tripwire only returns once the slow
guardrail is parked on its sleep with the CancelledError handler
installed. Without this, a fast scheduling order could cancel the slow
task before it reached the try block, leaving cancelled_observed unset
despite the production fix being correct.
@Quratulain-bilal
Copy link
Copy Markdown
Contributor Author

fixed in 9ecf9b9. Added a slow_started = asyncio.Event() that the slow guardrail sets before its await asyncio.sleep(5), and fast_tripwire now await slow_started.wait() before returning the tripwire. This guarantees
the slow task is parked on the sleep with its CancelledError handler installed before the cancel fires, so the test
now deterministically proves an in-flight sibling guardrail is awaited after cancellation.

adityasingh2400 added a commit to adityasingh2400/openai-agents-python that referenced this pull request May 8, 2026
run_input_guardrails / run_output_guardrails iterate guardrail tasks via
asyncio.as_completed and only cancel siblings on the tripwire branch. If a
guardrail raises a non-tripwire exception, the unfinished sibling tasks are
left scheduled and continue running past the function's return — wasting
work, possibly producing observable side effects, and triggering "Task was
destroyed but it is pending" warnings at loop shutdown.

Wrap the as_completed loop in try / except BaseException, and on any exit
that is not a normal return cancel still-running siblings and gather them
(return_exceptions=True) before re-raising. This is distinct from PR openai#3187,
which targets the tripwire path of run_output_guardrails.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working feature:core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants