Skip to content

Comments

Auto-deny unresolved tool approvals + tool-approval docs#230

Open
sethconvex wants to merge 1 commit intomainfrom
auto-deny-unresolved-approvals
Open

Auto-deny unresolved tool approvals + tool-approval docs#230
sethconvex wants to merge 1 commit intomainfrom
auto-deny-unresolved-approvals

Conversation

@sethconvex
Copy link
Contributor

@sethconvex sethconvex commented Feb 21, 2026

Summary

  • Add autoDenyUnresolvedApprovals() to inject synthetic denial responses for unresolved tool-approval-request parts instead of silently dropping orphaned tool calls
  • Update filterOutOrphanedToolMessages() to preserve tool-calls with pending approval requests (auto-deny handles them downstream)
  • Wire up auto-deny in fetchContextWithPrompt() around mergeApprovalResponseMessages()
  • Add comprehensive unit tests for the new function and update existing filter test expectations
  • Create docs/tool-approval.mdx documenting the full tool approval feature

Test plan

  • All new autoDenyUnresolvedApprovals unit tests pass (no unresolved, single, multiple, mixed, console.warn)
  • Updated filterOutOrphanedToolMessages test passes with new expectations
  • Existing approval E2E tests (example/convex/approval.test.ts) still pass
  • npm run typecheck — no type errors
  • npm run lint — clean on all changed files

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added comprehensive guide for implementing and managing tool approvals, including configuration, server-side and client-side workflows, and UI state handling.
  • Bug Fixes

    • Improved tool approval workflow with automatic denial of unresolved approvals when new generation starts, ensuring proper message retention and approval handling.

When a user sends a new message instead of resolving pending tool approvals,
inject synthetic denial responses rather than silently dropping the orphaned
tool calls. This preserves context in the message history and prevents the
AI SDK from receiving incomplete tool-call/result pairs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

This PR introduces a tool approval feature with automatic denial handling. It adds documentation for the approval workflow, implements autoDenyUnresolvedApprovals function to synthetically deny pending approvals, updates message filtering logic to retain tool calls with unresolved approvals, and includes comprehensive tests for the new functionality.

Changes

Cohort / File(s) Summary
Documentation
docs/tool-approval.mdx
New comprehensive guide covering tool approval feature definition, four-step server-side flow, client-side UI handling with useUIMessages, code examples for mutations/actions, and tool part state guidance.
Core Approval Logic
src/mapping.ts, src/mapping.test.ts
Added public autoDenyUnresolvedApprovals() function that scans messages for unresolved tool-approval-requests and injects synthetic denial responses grouped by assistant message; includes test suite validating no-op behavior, single/multiple denial injection, selective denial of unresolved approvals, and console.warn emission.
Message Filtering
src/client/search.ts, src/client/search.test.ts
Updated filterOutOrphanedToolMessages to retain tool calls with pending approval requests; wrapped merged context messages with autoDenyUnresolvedApprovals at two call sites to suppress unresolved approvals downstream; updated test to expect tool-call retention rather than filtering when approval-request lacks response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #216: Implements the complementary tool-approval workflow infrastructure (tool-approval-request/response message types) that this PR consumes with filtering and auto-deny logic in mapping.ts and client/search.ts.

Poem

🐰 A rabbit hops through approval flows,
Where tools must pause and wait,
When answers hang in uncertain rows,
Auto-deny won't hesitate!
Denials synthesized with care—
Hop forward with flair! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: introducing auto-deny functionality for unresolved tool approvals and adding comprehensive documentation for the tool-approval feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch auto-deny-unresolved-approvals

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/get-convex/agent/@convex-dev/agent@230

commit: 09a0ac0

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
docs/tool-approval.mdx (1)

117-128: continueAfterApprovals uses promptMessageId for the last approval message — verify this is the intended resume pattern.

Passing lastApprovalMessageId as promptMessageId means the agent will treat the approval message as the "prompt" to resume from. This is a somewhat non-obvious API pattern. The doc could benefit from a brief note explaining why the approval message ID is used as the prompt message ID (i.e., it tells the agent to resume from where the approval was issued).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tool-approval.mdx` around lines 117 - 128, The code passes
lastApprovalMessageId into approvalAgent.streamText as promptMessageId which is
non-obvious; update the continueAfterApprovals declaration (or surrounding docs)
to add a concise comment explaining that passing the approval message ID as
promptMessageId tells the agent to resume generation from the approval message,
and ensure the handler signature or argument naming (continueAfterApprovals
args: lastApprovalMessageId) remains consistent with that explanation so future
readers see why approvalAgent.streamText(ctx, { threadId }, { promptMessageId:
lastApprovalMessageId }) is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/tool-approval.mdx`:
- Around line 148-186: The snippet in Chat references lastApprovalMessageIdRef
and uses useEffect/useRef but never declares or imports them; update the Chat
example to either (a) add the missing import for React hooks (useEffect, useRef)
and declare const lastApprovalMessageIdRef = useRef<string | null>(null) inside
the Chat component before it’s used, or (b) explicitly add a brief inline
comment like “// useRef setup omitted for brevity: lastApprovalMessageIdRef
declared via useRef” next to the usages so readers understand the ref
exists—ensure references to lastApprovalMessageIdRef, useEffect, and useRef
match the symbols in the snippet.

---

Nitpick comments:
In `@docs/tool-approval.mdx`:
- Around line 117-128: The code passes lastApprovalMessageId into
approvalAgent.streamText as promptMessageId which is non-obvious; update the
continueAfterApprovals declaration (or surrounding docs) to add a concise
comment explaining that passing the approval message ID as promptMessageId tells
the agent to resume generation from the approval message, and ensure the handler
signature or argument naming (continueAfterApprovals args:
lastApprovalMessageId) remains consistent with that explanation so future
readers see why approvalAgent.streamText(ctx, { threadId }, { promptMessageId:
lastApprovalMessageId }) is used.

Comment on lines +148 to +186
```tsx
import { useUIMessages, type UIMessage } from "@convex-dev/agent/react";
import type { ToolUIPart } from "ai";

function Chat({ threadId }: { threadId: string }) {
const { results: messages } = useUIMessages(
api.chat.approval.listThreadMessages,
{ threadId },
{ initialNumItems: 10, stream: true },
);

const submitApproval = useMutation(api.chat.approval.submitApproval);
const triggerContinuation = useMutation(
api.chat.approval.triggerContinuation,
);

const hasPendingApprovals = messages.some((m) =>
m.parts.some(
(p) =>
p.type.startsWith("tool-") &&
(p as ToolUIPart).state === "approval-requested",
),
);

// When all approvals are resolved, trigger continuation
useEffect(() => {
if (!hasPendingApprovals && lastApprovalMessageIdRef.current) {
void triggerContinuation({
threadId,
lastApprovalMessageId: lastApprovalMessageIdRef.current,
});
lastApprovalMessageIdRef.current = null;
}
}, [hasPendingApprovals, threadId, triggerContinuation]);

// Render approval buttons for tool parts with state "approval-requested"
// ...
}
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Client snippet is incomplete — consider adding a note or expanding it.

The React example references lastApprovalMessageIdRef (line 174, 177, 179) without declaring it, and omits useEffect/useRef imports. While documentation snippets are often abbreviated, this particular example may confuse readers since the ref is central to the continuation logic. Consider either adding a brief // ... useRef setup omitted for brevity comment or showing the ref declaration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tool-approval.mdx` around lines 148 - 186, The snippet in Chat
references lastApprovalMessageIdRef and uses useEffect/useRef but never declares
or imports them; update the Chat example to either (a) add the missing import
for React hooks (useEffect, useRef) and declare const lastApprovalMessageIdRef =
useRef<string | null>(null) inside the Chat component before it’s used, or (b)
explicitly add a brief inline comment like “// useRef setup omitted for brevity:
lastApprovalMessageIdRef declared via useRef” next to the usages so readers
understand the ref exists—ensure references to lastApprovalMessageIdRef,
useEffect, and useRef match the symbols in the snippet.

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.

1 participant