Skip to content

fix(consent): request-consent skips caps with a pending Decision#1432

Merged
jaylfc merged 1 commit into
devfrom
fix/request-consent-skip-pending
Jun 24, 2026
Merged

fix(consent): request-consent skips caps with a pending Decision#1432
jaylfc merged 1 commit into
devfrom
fix/request-consent-skip-pending

Conversation

@jaylfc

@jaylfc jaylfc commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Gap

request-consent (#1430) de-dupes against granted/denied caps but not against caps that already have an unanswered app_grant Decision. The lazy first-use path (approved, timing = BOTH) re-fires on every denied capability access until the user answers, so repeated calls would pile up duplicate pending consent cards for the same app + cap.

Fix

Before building the consent card, scan the user's pending app_grant Decisions for this app and exclude those capabilities (alongside the existing free-cap and already-decided filtering). Hardens the primitive before its callers (install flow + broker lazy-escalation) wire in during the live session.

Test

Second call for the same cap before answering returns {decision: null, pending: []}; after answering, a request for a different cap still raises its own card. 34 related tests pass.

Summary by CodeRabbit

  • Bug Fixes
    • Consent requests now avoid creating duplicate pending prompts for the same app and capability when a decision is already awaiting action.
    • If only already-handled or pending capabilities remain, the app now returns no new consent card.
    • Requests for a different capability still generate a new pending consent prompt as expected.

The lazy first-use path re-fires request-consent on every denied capability
access until the user answers. Without skipping caps that already have an
unanswered app_grant Decision for the app, each call piled up a duplicate
pending consent card. Now scan the user's pending app_grant Decisions for this
app and exclude those caps too.
@jaylfc jaylfc enabled auto-merge (squash) June 24, 2026 20:31
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67b5a2bc-bf86-4a1f-ad77-d11f044528c1

📥 Commits

Reviewing files that changed from the base of the PR and between f824760 and cffe6ce.

📒 Files selected for processing (2)
  • tests/test_routes_app_permissions.py
  • tinyagentos/routes/app_permissions.py

📝 Walkthrough

Walkthrough

The request-consent route now ignores capabilities that already have pending app_grant decisions for the same app. A new async test covers repeated requests for the same capability and a later request for a different capability.

Changes

App consent request deduplication

Layer / File(s) Summary
Pending decision lookup
tinyagentos/routes/app_permissions.py
The consent flow queries pending decisions and collects app_grant capability metadata for the current app_id.
Skip pending capabilities and validate
tinyagentos/routes/app_permissions.py, tests/test_routes_app_permissions.py
The pending capability filter excludes already awaiting app_grant decisions, and the test covers repeated consent requests plus a new capability request after answering the original decision.

Estimated review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • jaylfc/taOS#1429: Adds the decision metadata and app_grant payload shape that this change reads to identify pending consent decisions.
  • jaylfc/taOS#1430: Updates the same /api/apps/{app_id}/request-consent flow and includes related tests in tests/test_routes_app_permissions.py.

Poem

🐇 I hop through consent paths, light and spry,
No duplicate cards when one’s still nearby.
For one app’s whisper, I wait and see,
Then grant a new path for a different key.
✨ Hoppity!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: request-consent now skips capabilities with a pending Decision.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/request-consent-skip-pending

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.

@gitar-bot

gitar-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

Note

Your trial team has used its Gitar budget, so automatic reviews are paused. Upgrade now to unlock full capacity. Comment "Gitar review" to trigger a review manually.
Learn more about usage limits

Code Review ✅ Approved

Prevents redundant consent card generation by filtering capabilities with pending app_grant decisions during the request process. All associated tests passed with no remaining issues.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Important

Your trial ends in 2 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

pending: list[str] = []
for c in caps:
if c in FREE_CAPS or c in decided or c in pending:
if c in FREE_CAPS or c in decided or c in awaiting or c in pending:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Race window — two concurrent request-consent calls for the same app can both pass the awaiting check and each create a duplicate pending Decision.

The new scan at lines 150-154 closes the lazy re-fire gap (sequential calls from the broker) but does not close the concurrent-call gap: between await store.list(...) (line 151) and await store.create(...) (line 165) there is no lock, so two simultaneous first-time requests for the same app + cap will both observe an empty awaiting set and both produce a card. The PR description focuses on the lazy re-fire scenario, so this is a future-risk worth a follow-up (e.g. a UNIQUE constraint on (user_id, app_id, capability, status='pending'), or a single SQL upsert keyed on a pending app_grant).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

# so skip them or it would pile up duplicate pending consent cards.
store = request.app.state.decision_store
awaiting: set[str] = set()
for d in await store.list(status="pending", user_id=user.user_id):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Loading every pending decision for the user (up to the 500-row limit in DecisionStore.list) on every request-consent call is wasteful when the user has many non-app-grant pending decisions.

Filtering by kind == "app_grant" and app_id == ... is currently done in Python after a full table scan + JSON decode. If app_grant decisions ever become a meaningful fraction of the inbox this will dominate the request path. A small SQL filter (e.g. WHERE status='pending' AND json_extract(metadata, '$.kind') = 'app_grant' AND json_extract(metadata, '$.app_id') = ?) would push the predicates into SQLite and avoid the Python-side loop and decode.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/app_permissions.py 159 Race window between awaiting scan and store.create — concurrent request-consent calls for the same app can still both produce duplicate pending Decisions.

SUGGESTION

File Line Issue
tinyagentos/routes/app_permissions.py 151 Loading all pending user decisions (up to 500) and filtering in Python is wasteful; push the kind/app_id predicates into SQLite via json_extract.
Files Reviewed (2 files)
  • tinyagentos/routes/app_permissions.py - 2 issues
  • tests/test_routes_app_permissions.py - 0 issues

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 56.7K · Output: 6.2K · Cached: 322.5K

@jaylfc jaylfc merged commit 7acf72a into dev Jun 24, 2026
10 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant