Skip to content

fix(approval): classify resolved decide races#4242

Open
YOMXXX wants to merge 4 commits into
tinyhumansai:mainfrom
YOMXXX:fix/GH-4176-approval-no-pending
Open

fix(approval): classify resolved decide races#4242
YOMXXX wants to merge 4 commits into
tinyhumansai:mainfrom
YOMXXX:fix/GH-4176-approval-no-pending

Conversation

@YOMXXX

@YOMXXX YOMXXX commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Distinguishes approval decisions that hit an already-terminal row from request ids that were never persisted.
  • Keeps duplicate/expired approval_decide calls as benign expected errors while preserving Sentry signal for genuine missing registrations.
  • Adds an explicit ApprovalAlreadyResolved observability bucket for the known no pending approval found race shape.
  • Extends Rust unit and JSON-RPC E2E coverage for duplicate, expired, and never-persisted approval decide paths.
  • Includes current Markdown link fixes needed for CI on branches based on upstream/main.

Problem

Solution

  • Added ApprovalDecisionOutcome and decide_with_status so the store/gate can classify Decided, AlreadyTerminal, and NotFound without breaking the existing decide wrapper.
  • Updated approval_decide to return classifiable no pending approval found ... (already decided as ...) only for terminal replays, and a distinct approval request not found ... error for never-persisted ids.
  • Added ExpectedErrorKind::ApprovalAlreadyResolved for the benign replay string only.
  • Added store, observability, and JSON-RPC E2E assertions covering duplicate decide, expired rows, and unknown ids.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines are covered by focused Rust unit and E2E tests; CI remains the enforced diff-cover gate.
  • Coverage matrix updated — N/A: behavior-only approval/observability fix, no feature row added/removed/renamed.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no coverage-matrix feature ID applies.
  • No new external network dependencies introduced (mock/local harness only)
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md) — N/A: approval RPC error classification only.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime/platform impact: Rust core approval store/gate/RPC and central observability classification only.
  • Sentry impact: duplicate or expiry-race approval_decide failures are demoted as expected; never-persisted request ids remain reportable under a different message.
  • User-visible effect: duplicate/late approval actions still fail instead of re-running any tool; the error text now identifies already-terminal vs missing request ids.
  • Docs impact: localized README links and the Cursor cloud-agent docs link are refreshed for the existing Markdown link gate.
  • No migration, performance, or security weakening. The approval gate remains fail-closed.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4176-approval-no-pending
  • Commit SHA: 8af9e22d8d75483a8af7c9b38ba15fb3d9f8be12

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no app TS/CSS changes.
  • pnpm typecheck — N/A: no frontend TypeScript changes.
  • Focused tests:
    • RED: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib decide_with_status_distinguishes_terminal_expired_and_missing_rows failed before implementation on missing decide_with_status / ApprovalDecisionOutcome.
    • RED: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib classifies_resolved_approval_decide_race_as_expected failed before implementation on missing ApprovalAlreadyResolved.
    • RED: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib classifies_resolved_approval_decide_race_as_expected failed before review fix because prefix-only messages were over-classified.
    • RED: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib decide_with_status_releases_waiter_for_already_terminal_row failed before review fix because the parked waiter timed out.
    • GREEN: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib decide_with_status_distinguishes_terminal_expired_and_missing_rows
    • GREEN: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib classifies_resolved_approval_decide_race_as_expected
    • GREEN: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --lib decide_with_status_releases_waiter_for_already_terminal_row
    • GREEN: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml --test tool_registry_approval_raw_coverage_e2e approval_rpc_decision_paths_persist_always_allow_and_recent_audit
  • Rust fmt/check (if changed):
    • cargo fmt --manifest-path Cargo.toml --check
    • git diff --check
    • GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml
  • Tauri fmt/check (if changed): N/A: no Tauri crate changes.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: already-decided/expired approval requests classify as expected approval replay races; request ids that never existed remain observable.
  • User-visible effect: duplicate/late decisions keep failing safely, but now surface a more precise already-terminal error; unknown ids surface approval request not found.

Parity Contract

  • Legacy behavior preserved: successful decisions still publish ApprovalDecided, resolve waiters, and persist ApproveAlwaysForTool; the existing decide wrapper still returns Option<PendingApproval> for existing call sites.
  • Guard/fallback/dispatch parity checks: JSON-RPC E2E covers duplicate vs unknown dispatch text, store tests cover duplicate/expired/missing classification, and observability tests prove only the benign replay string is demoted.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features
    • Approval decision handling now reports richer outcome states (newly processed, already resolved/terminal, or missing request).
  • Bug Fixes
    • Prevented already-resolved approval replays/race scenarios from appearing as generic errors.
    • Updated RPC and UI-facing error messaging so unknown request IDs report “not found” rather than “no pending approval.”
  • Tests
    • Added regression coverage for outcome classification and extended E2E coverage for missing-request decision paths.
  • Documentation
    • Updated documentation links/embeds across multiple translations and refreshed an agent workflow URL.

@YOMXXX YOMXXX requested a review from a team June 28, 2026 17:51
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ecd251fd-fc38-40d4-af0c-c3a126374994

📥 Commits

Reviewing files that changed from the base of the PR and between 9008f21 and 8af9e22.

📒 Files selected for processing (2)
  • src/core/observability.rs
  • src/openhuman/approval/gate.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/approval/gate.rs
  • src/core/observability.rs

📝 Walkthrough

Walkthrough

Adds typed approval decision outcomes through store, gate, and RPC handling, classifies benign approval replay messages in observability, and updates localized documentation links.

Changes

Approval Decision Outcome Classification

Layer / File(s) Summary
Outcome type and export
src/openhuman/approval/types.rs, src/openhuman/approval/mod.rs
Defines ApprovalDecisionOutcome and re-exports it from the approval module.
Store decision outcome flow
src/openhuman/approval/store.rs
Adds terminal-decision lookup helpers, implements decide_with_status, refactors decide to delegate, and adds store tests for decided, already-terminal, and not-found outcomes.
Gate and RPC outcome handling
src/openhuman/approval/gate.rs, src/openhuman/approval/rpc.rs, tests/tool_registry_approval_raw_coverage_e2e.rs
Adds ApprovalGate::decide_with_status, rewrites ApprovalGate::decide to map outcome variants, updates approval_decide to match on ApprovalDecisionOutcome, and adds an end-to-end not-found assertion.
Approval replay demotion
src/core/observability.rs
Adds ExpectedErrorKind::ApprovalAlreadyResolved, classifies matching approval replay messages early, logs them at info, and adds a regression test.

Documentation Link Updates

Layer / File(s) Summary
Localized README link edits
docs/README.de.md, docs/README.ja-JP.md, docs/README.ko.md, docs/README.ur-pk.md, docs/README.zh-CN.md
Adjusts Reddit link presentation, native voice documentation paths, and Star History embed URLs in localized README files.
Cursor Cloud Agents link
docs/agent-workflows/cursor-cloud-agents.md
Updates the linked documentation URL in the Cursor Cloud Agents workflow guide.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

rust-core, bug

Poem

🐇 A rabbit hops through links and code,
Approval races found their mode.
Some errors now just wink and gleam,
While docs update the breadcrumb stream.
Noisy sighs fade softly away,
And stars and tools point the right way.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several README translation and cursor-cloud-agent doc edits are unrelated to approval race handling. Move the documentation-only edits to a separate PR or remove them from this approval fix.
✅ 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: handling resolved approval decision races.
Linked Issues check ✅ Passed Implements the requested benign/terminal-vs-not-found approval classification and observability demotion for [#4176].
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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.

@coderabbitai coderabbitai Bot added bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. sentry-traced-bug Bug identified via Sentry triage labels Jun 28, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/tool_registry_approval_raw_coverage_e2e.rs (1)

1295-1311: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The expired RPC path still isn't pinned end-to-end.

This adds the unknown-id assertion, but the same E2E still never drives openhuman.approval_decide against a request that became terminal via TTL expiry. The store unit test covers that branch; the JSON-RPC contract introduced in this PR does not. Add an expired-request assertion here too so duplicate, expired, and unknown stay distinct at the RPC layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/tool_registry_approval_raw_coverage_e2e.rs` around lines 1295 - 1311,
The approval_decide E2E coverage in the tool registry test still misses the
TTL-expired terminal case, so add an assertion that drives
openhuman.approval_decide against a request that has expired and verifies its
RPC error is distinct from both “approval request not found” and “no pending
approval”. Reuse the existing rpc and error_message helpers in the test around
the approval_decide flow, and add the expired-request setup/assertion near the
current unknown-id check so duplicate, expired, and unknown paths are all pinned
end-to-end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/observability.rs`:
- Around line 557-559: The replay matcher in
is_approval_already_resolved_message is too broad because it only checks for the
generic “no pending approval found for request_id” text. Tighten the helper to
require the replay-specific “already decided as” suffix as well, so only the
intended replay path is matched. Also add a negative test around the
observability matching logic to ensure a prefix-only message does not get
classified as already resolved.

In `@src/openhuman/approval/gate.rs`:
- Around line 719-740: The non-`Decided` branch in `approval::gate` only logs,
leaving any parked waiter unresolved until TTL. Update the `AlreadyTerminal` and
`NotFound` handling in the decision path so the local waiter is released
immediately: in `AlreadyTerminal`, forward the persisted decision to the parked
future, and in `NotFound`, drop the sender so the existing cleanup path can run.
Use the existing `ApprovalDecisionOutcome` match in `gate.rs` to keep the
behavior consistent with the `Decided` flow.

---

Nitpick comments:
In `@tests/tool_registry_approval_raw_coverage_e2e.rs`:
- Around line 1295-1311: The approval_decide E2E coverage in the tool registry
test still misses the TTL-expired terminal case, so add an assertion that drives
openhuman.approval_decide against a request that has expired and verifies its
RPC error is distinct from both “approval request not found” and “no pending
approval”. Reuse the existing rpc and error_message helpers in the test around
the approval_decide flow, and add the expired-request setup/assertion near the
current unknown-id check so duplicate, expired, and unknown paths are all pinned
end-to-end.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60ccf6f7-dcd2-4250-a3ed-fb09b692206f

📥 Commits

Reviewing files that changed from the base of the PR and between 5a41a4f and 3202f1f.

📒 Files selected for processing (7)
  • src/core/observability.rs
  • src/openhuman/approval/gate.rs
  • src/openhuman/approval/mod.rs
  • src/openhuman/approval/rpc.rs
  • src/openhuman/approval/store.rs
  • src/openhuman/approval/types.rs
  • tests/tool_registry_approval_raw_coverage_e2e.rs

Comment thread src/core/observability.rs
Comment thread src/openhuman/approval/gate.rs
@coderabbitai coderabbitai Bot removed sentry-traced-bug Bug identified via Sentry triage bug labels Jun 28, 2026
@coderabbitai coderabbitai Bot added the bug label Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Approval: classify 'no pending approval found' benign race + distinguish genuine loss (TAURI-RUST-5EH)

1 participant