fix(core): fail closed on non-bool needs_approval predicate results - #4848
fix(core): fail closed on non-bool needs_approval predicate results#4848xixi-box wants to merge 3 commits into
Conversation
|
Quick context for reviewers, since #4846 and #4847 also propose fixes for #4845 and I'd like to make comparison easy: Scope and precedent. This follows the fail-closed precedent the maintainers set for the sibling issue #3863 (fixed in #3867 by treating uninspectable arguments as unanswered), which is also the semantics I proposed in the #4845 thread when I took the issue. A non-bool result is the same kind of non-answer, so the two cases now behave consistently. One shared helper, every surface. The rule lives in Compatibility. Predicates returning actual booleans keep exact v0.22.0 behavior, and truthy non-bool returns (e.g. a reason string meaning "yes") keep requesting approval — only the falsy half, the dangerous one, flips from silent execution to an approval interruption. The public Tests. Regression tests cover both Runner ( Verification. Beyond the focused suites, this went through two independent clean reviews on the full diff (approval-gate security and compatibility/parity specialties, with sibling scans over every |
sylvesterkaczmarek
left a comment
There was a problem hiding this comment.
Hi, this fixes the shared function/Realtime evaluator, but MCP approval policies still use _get_needs_approval_for_tool() in src/agents/mcp/server.py, which ends in return bool(result). A policy returning None there can still become False and run without approval. Could MCP route through evaluate_needs_approval_setting() too, with an MCP regression?
|
Good catch — that site had the same defect and is now fixed in 69ce2ba.
Regression coverage added in
Thanks for flagging the gap. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 69ce2ba54a
ℹ️ 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".
| captured["tool"] = tool | ||
| return None | ||
|
|
||
| server = FakeMCPServer(require_approval=async_policy if async_policy else sync_policy) |
There was a problem hiding this comment.
Preserve the parametrized sync-policy selector
When the async_policy=False case runs, the nested async def async_policy has already rebound the parameter name, so this condition is always truthy and both parametrized cases install the async policy. The newly changed sync non-bool MCP path therefore remains untested despite the sync case label; rename the boolean selector (for example, to is_async) so the false case selects sync_policy.
AGENTS.md reference: AGENTS.md:L156-L156
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2c9a625 — thanks.
The local async def async_policy was shadowing the async_policy boolean parameter, so async_policy if async_policy else sync_policy always selected the async policy and the sync parametrized case was silently testing the async path. The selector is renamed to is_async, so the sync case now exercises the non-bool sync policy path through the shared evaluator and the async case the awaitable one; both pass.
|
@codex review The two follow-up commits since your last review pass may be worth another look: 69ce2ba routes the MCP callable |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
This pull request fixes #4845 by making a callable
needs_approvalpredicate fail closed when its resolved return value is not abool.A predicate that falls off the end of a branch returns
None, andevaluate_needs_approval_settingcoerced the result withbool(), sobool(None)becameFalseand the guarded tool executed without any approval request. Every other unanswerable approval question in the feature already fails closed: a predicate that raises is caught in tool planning, and uninspectable arguments short-circuit to "requires approval" (the fix for #3863 in #3867). A non-bool result is the same kind of non-answer, so the shared helper now treats any resolved non-bool result (for exampleNone,"", or0) as requiring approval.The change is a 5-line rule in the single shared helper (
src/agents/util/_approvals.py), so it applies uniformly to Runner function tools, shell tools, apply-patch tools, custom tools, and Realtime sessions. MCP callablerequire_approvalpolicies are routed through the same helper insrc/agents/mcp/server.py, so a policy returning a non-bool there also fails closed. Behavior for predicates that return actual booleans is unchanged, and truthy non-bool returns keep the released outcome of requesting approval; only falsy non-bool results flip from silent execution to an approval interruption.strictcontinues to govern only invalid setting types, so thestrict=FalseRealtime path gets the same fail-closed rule.A separately timed docs-only PR will extend the existing fail-closed sentence in
docs/human_in_the_loop.mdto cover non-bool results, since that wording describes behavior not yet in a published release.Test plan
tests/test_hitl_error_scenarios.py: parametrized async predicate returningNone,"",0, and a truthy string (the truthy case pins the released compat behavior), plus a sync-predicateNonecase, all asserting an approval interruption with the tool not executed.tests/realtime/test_session.pyasserting aNonepredicate result producesRealtimeToolApprovalRequiredand does not invoke the tool.tests/mcp/test_mcp_util.py: sync and asyncrequire_approvalpolicies returningNonerequire approval (the policy still runs and receives the tool), and a sync policy returningFalsekeeps the released run behavior.returns Nonerow changes fromran=YES -> no approval requestedtoasked for approval, while all three control rows are unchanged.Issue number
Fixes #4845
Checks
.agents/skills/code-change-verification/scripts/run.sh/reviewbefore submitting this PR