Skip to content

fix(agent): enforce approval gate on allowFinalResponse path and normalize approval-predicate args#55

Open
lullu57 wants to merge 1 commit into
OpenRouterTeam:mainfrom
lullu57:fix/approval-gate-final-response
Open

fix(agent): enforce approval gate on allowFinalResponse path and normalize approval-predicate args#55
lullu57 wants to merge 1 commit into
OpenRouterTeam:mainfrom
lullu57:fix/approval-gate-final-response

Conversation

@lullu57

@lullu57 lullu57 commented Jun 26, 2026

Copy link
Copy Markdown

TL;DR

Fixes two approval-gate gaps in the agent loop (only affects apps that use requireApproval). Closes #54.

  1. allowFinalResponse skipped the approval check. When stopWhen halts the loop on a turn that still has pending tool calls and allowFinalResponse is enabled, those calls were executed via executeToolRound(...) directly — without the handleApprovalCheck/partitionToolCalls step the main loop runs before every other round. An approval-gated tool could therefore run unprompted on the final-response turn. This PR runs the normal approval check before executing those pending calls.
  2. Approval predicate saw pre-normalization args. Function-form requireApproval predicates received the raw model-produced arguments, while execute() received the Zod-parsed input (defaults applied, values coerced). The predicate now sees the schema-normalized arguments, so the approval decision matches what the tool actually runs with.

Files

  • Core: packages/agent/src/lib/model-result.ts, packages/agent/src/lib/conversation-state.ts
  • Tests: packages/agent/tests/unit/allow-final-response.test.ts (adds the first approval-path coverage for this branch), packages/agent/tests/unit/conversation-state.test.ts
  • .changeset/approval-gate-final-response.md (patch)

Risk / reviewer notes

  • The normal approve/reject path and apps that don't use approval gates are unchanged.
  • Local checks pass: pnpm run lint (Biome), pnpm run typecheck, pnpm run test (297/297). I could not run the sentrux structural-gate job locally; the change is small and stays within the lib/ layer, but please confirm that gate in CI.

Background and a fuller write-up are in #54.

…alize approval-predicate args

The allowFinalResponse path previously executed the pending tool calls left
over when stopWhen halted the loop without first running the approval
partitioning the main loop applies to every other round. It now runs the
normal approval check before executing those pending calls, so an
approval-gated tool can no longer run unprompted on the final-response turn.

Separately, function-form requireApproval predicates were evaluated against
the raw model-produced arguments while the tool's execute() received the
Zod-parsed input (defaults applied, values coerced). The predicate now sees
the schema-normalized arguments, so the approval decision matches the input
the tool actually executes with.
@lullu57 lullu57 force-pushed the fix/approval-gate-final-response branch from d3569e7 to ad9c2a3 Compare June 26, 2026 03:04
perry-the-pr-reviewer[bot]

This comment was marked as outdated.

@perry-the-pr-reviewer perry-the-pr-reviewer 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.

Perry's Review

Fixes two approval-gate gaps: executes the approval check before running pending tool calls on the allowFinalResponse path, and normalizes tool-call arguments via schema-parsing before evaluating function-form requireApproval predicates.

Verdict: 💬 Comments / questions

Details

Risk: 🟡 Medium — approval-gate behavioral change in agent loop; confined to callers using requireApproval, well-tested

CI: no checks reported on branch

Findings:

  • No new findings since prior review (diff identical to d3569e7)
  • 2 prior suggestions remain open (see existing review threads below)

Security: no concerns — categories 1–8 not touched; library-internal approval-predicate change

Test coverage: new tests cover allowFinalResponse approval path and schema-defaults normalization for tool-level predicates; callLevelCheck path for schema-normalized args is still untested (open thread)

Unresolved threads: 2 open Perry suggestion threads from prior review

Review metadata

Scope: incremental — 0 new lines since prior review (diff unchanged from d3569e7)
Review: tier=small · model=claude-sonnet-latest · score=1.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Approval gate not enforced on the allowFinalResponse path; predicate also sees pre-normalization args

1 participant