Skip to content

fix(run): cancel sibling guardrail tasks when one raises#3239

Draft
adityasingh2400 wants to merge 1 commit intoopenai:mainfrom
adityasingh2400:fix/runguard-leak-tasks-on-exc
Draft

fix(run): cancel sibling guardrail tasks when one raises#3239
adityasingh2400 wants to merge 1 commit intoopenai:mainfrom
adityasingh2400:fix/runguard-leak-tasks-on-exc

Conversation

@adityasingh2400
Copy link
Copy Markdown
Contributor

Summary

run_input_guardrails and run_output_guardrails in src/agents/run_internal/guardrails.py iterate guardrail tasks via asyncio.as_completed and only cancel siblings on the tripwire branch. If one guardrail raises a non-tripwire exception, the loop exits but unfinished sibling tasks are left scheduled and continue running past the function's return — wasting work, possibly producing observable side effects (e.g. an LLM-based guardrail finishing a network request after the caller already saw a different exception), and triggering Task was destroyed but it is pending! warnings at event-loop shutdown.

The fix wraps the as_completed loop in try / except BaseException and, on any exit that is not a normal return, cancels still-running siblings and gathers them (return_exceptions=True) before re-raising. This is distinct from #3187, which targets the tripwire branch of run_output_guardrails; this PR targets the raise branch in both functions.

Repro

async def raising(ctx, agent, input): raise RuntimeError("boom")
async def slow(ctx, agent, input):
    await asyncio.sleep(0.5)        # leaks past run_input_guardrails return
    print("oops, ran after caller raised")
    return GuardrailFunctionOutput(None, False)

await run_input_guardrails(agent, [InputGuardrail(slow), InputGuardrail(raising)], "x", ctx)
# raises RuntimeError, but `slow` keeps running.

Test plan

  • New test_input_guardrail_raise_cancels_siblings — asserts the slow sibling is cancelled (not completed) by the time the raise propagates.
  • New test_output_guardrail_raise_cancels_siblings — same for output guardrails.
  • Existing 37 guardrail tests still pass (tests/test_guardrails.py).
  • tests/test_runner_guardrail_resume.py, tests/test_stream_input_guardrail_timing.py, tests/test_tool_guardrails.py still pass.
  • ruff check passes for changed files.

🤖 Generated with Claude Code

@seratch
Copy link
Copy Markdown
Member

seratch commented May 8, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ 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.

can you resolve the conflicts?

@seratch seratch added this to the 0.17.x milestone May 8, 2026
@seratch seratch marked this pull request as draft May 8, 2026 17:38
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>
@adityasingh2400 adityasingh2400 force-pushed the fix/runguard-leak-tasks-on-exc branch from bd17e44 to 8a29e3b Compare May 8, 2026 18:01
@adityasingh2400
Copy link
Copy Markdown
Contributor Author

Rebased onto current main; conflicts resolved (combined with #3187's tripwire-branch await). Tests + lint clean.

@seratch seratch marked this pull request as ready for review May 9, 2026 02:26
@seratch seratch marked this pull request as draft May 9, 2026 03:42
@seratch seratch removed this from the 0.17.x milestone May 10, 2026
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