Skip to content

fix(approvals): consult stored status before needs_approval on execute#3937

Draft
hsusul wants to merge 2 commits into
openai:mainfrom
hsusul:fix/execute-approval-status-before-needs-approval
Draft

fix(approvals): consult stored status before needs_approval on execute#3937
hsusul wants to merge 2 commits into
openai:mainfrom
hsusul:fix/execute-approval-status-before-needs-approval

Conversation

@hsusul

@hsusul hsusul commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This pull request fixes HITL tool execution so resolved approval status is checked before dynamic needs_approval checkers run.

On resume (and for sticky approve/reject decisions), tool execution previously awaited needs_approval again before reading stored status. A checker that raises, hits the network, or returns a different result could abort or bypass an already-approved/rejected call. Planning helpers already short-circuited correctly (#3229 / #3259); the execute path for function tools, shell/custom/apply_patch actions, and realtime tool approval did not.

Changes:

  • Check get_approval_status first in _maybe_execute_tool_approval, shell/custom/apply_patch Action.execute, and realtime _maybe_request_tool_approval
  • Only evaluate needs_approval when status is still unresolved
  • Add a regression covering resume with a raising callable checker
  • Update MCP callable-approval tests that asserted the old double-invocation behavior

Test plan

  • Added test_execute_path_skips_needs_approval_checker_when_status_resolved
  • Confirmed the new test failed on unpatched main and passed after the fix
  • Ran .agents/skills/code-change-verification/scripts/run.sh (format, lint, typecheck, tests)
  • Confirmed all verification steps pass

Issue number

N/A — previously unreported; completes the execute-path half of the contract fixed for planning in #3229 / #3259.

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

Resume and sticky decisions were re-evaluating dynamic needs_approval
checkers before reading resolved approval status, so a checker that
raises or changes result could abort an already-approved tool call.

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution. The underlying issue is valid, and checking stored approval status before invoking a dynamic needs_approval policy is the correct contract.

Before merging, please add failing-on-base regression coverage for each independently changed execution path: function, shell, custom, apply_patch, and Realtime. At minimum, cover the safety-relevant sticky-rejection case where always_reject=True has been recorded and the callable later returns False; the tool must remain rejected and its executor must not run. A parameterized test for the Runner paths plus a focused Realtime test is sufficient.

The implementation direction otherwise looks sound, and no documentation or public API changes are required.

Add parameterized Runner regressions for function/shell/custom/apply_patch
and a Realtime case where always_reject wins over a later needs_approval
False result without re-entering the checker or running the executor.
@hsusul

hsusul commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I’ve added the requested regression coverage for all independently changed execution paths.

The Runner coverage is parameterized across function, shell, custom, and apply_patch, and I added a focused Realtime test. Each case verifies that a previously recorded always_reject=True decision remains authoritative when the later dynamic needs_approval callable returns False, and that neither the checker nor the underlying executor is invoked.

I also verified that the five new cases fail against the pre-fix execute-path behavior and pass with this change. Formatting, linting, type checking, and the full test suite are passing.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9326e53b93

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

Comment on lines +623 to +627
needs_approval = await self._function_needs_approval(function_tool, tool_call)
if self._closing or self._closed:
return None
if not needs_approval:
return True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Recheck sticky decisions after awaiting the approval checker

With the default async_tool_calls=True, two calls can evaluate approval concurrently: call B can read None here and suspend in a slow checker while call A becomes pending and is rejected with always=True. If B's checker then returns False, this path immediately returns True and executes B despite the newly stored sticky rejection. Re-read get_approval_status after the await, before either bypassing approval or creating another pending request, so concurrent decisions remain authoritative.

AGENTS.md reference: AGENTS.md:L106-L106

Useful? React with 👍 / 👎.

@seratch
seratch marked this pull request as draft July 24, 2026 11:21
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.

2 participants