Skip to content

fix(approval): route /approve through approval resolver#84678

Merged
kevinslin merged 1 commit into
mainfrom
codex/slack-approve-runtime-fix
May 20, 2026
Merged

fix(approval): route /approve through approval resolver#84678
kevinslin merged 1 commit into
mainfrom
codex/slack-approve-runtime-fix

Conversation

@kevinslin

@kevinslin kevinslin commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: the shared /approve command submitted approval decisions through the generic callGateway helper. That opened an ordinary transient Gateway request instead of the operator approvals resolver/client path, so approvals that require the trusted approval-runtime client context could be invisible and surface as unknown or expired.
  • Solution: route /approve submissions through resolveApprovalOverGateway. That helper connects via the operator approvals Gateway client path and sends approval decisions with the approval-runtime client context used by the exec/plugin approval managers.
  • What changed: updated the shared approve command handler to use the approval resolver, preserved exec approval behavior, kept plugin approval routing for plugin:<id> approvals, refreshed focused tests around resolver calls and fallback routing, and documented that /approve handles exec and plugin approvals.
  • What did NOT change (scope boundary): approval authorization policy, approver config shape, native approval delivery, and approval id formats are unchanged.

Motivation

  • Manual /approve should behave like native approval actions: it needs to resolve through the trusted approval runtime, not through a one-off generic Gateway call. The failure was observed from Slack with /approve plugin:<id>, but the fix is in the shared command path and applies to approval submission generally.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #
  • This PR fixes a bug or regression

Real behavior proof (required for external PRs)

  • Behavior addressed: manual channel /approve should resolve pending exec/plugin approvals through the approval resolver instead of returning unknown or expired approval id because the generic Gateway client path cannot see the approval.
  • Real environment tested: isolated local OpenClaw Gateway on macOS/Darwin arm64, Node v24.15.0. The proof used a Slack native command context against the real Gateway approval APIs and resolver path; it did not call the Slack network.
  • Exact steps or command run after this patch: node --import tsx --input-type=module <temporary proof harness> | tee /tmp/openclaw-pr-84678-approval-proof.txt; then node scripts/run-vitest.mjs src/auto-reply/reply/commands-approve.test.ts.
  • Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output):
proof started gateway_url=ws://127.0.0.1:52702
proof command_context=slack native /approve
proof approval_listener=ready scopes=operator.approvals internal_runtime=true
plugin request accepted status=accepted id=plugin:fe8734e7-2b24-4549-9bc3-6e9a675c0427
plugin command reply=✅ Approval allow-once submitted for plugin:fe8734e7-2b24-4549-9bc3-6e9a675c0427.
plugin waitDecision id=plugin:fe8734e7-2b24-4549-9bc3-6e9a675c0427 decision=allow-once
exec request accepted status=accepted id=proof-exec-3a13b093-ef6e-48cb-a09e-827bd7756e66
exec command reply=✅ Approval allow-once submitted for proof-exec-3a13b093-ef6e-48cb-a09e-827bd7756e66.
exec waitDecision id=proof-exec-3a13b093-ef6e-48cb-a09e-827bd7756e66 decision=allow-once
gateway events=plugin.approval.requested,plugin.approval.resolved,exec.approval.requested,exec.approval.resolved
proof result=pass

Focused test output:

node scripts/run-vitest.mjs src/auto-reply/reply/commands-approve.test.ts
Test Files  1 passed (1)
Tests       18 passed (18)
  • Observed result after fix: /approve plugin:<id> allow-once in a channel command context resolved a live pending plugin approval through plugin.approval.resolve, and /approve <exec-id> allow-once still resolved an exec approval through exec.approval.resolve. Gateway events included requested and resolved events for both approval families.
  • What was not tested: actual Slack app network slash-command callback delivery from Slack's servers; this proof invokes the Slack command handler locally while using a real Gateway and WebSocket approval clients.
  • Before evidence (optional but encouraged): prior manual Slack /approve plugin:<id> allow-once returned Failed to submit approval: unknown or expired approval id.

Root Cause (if applicable)

  • Root cause: /approve bypassed resolveApprovalOverGateway and called callGateway directly. The generic call did not use the operator approvals client path that supplies the trusted approval-runtime client context required by approval visibility checks.
  • Relevant contract: approval resolution filters pending approval records through the connected Gateway client. A trusted approval-runtime client is established by connecting as the Gateway backend approval client with operator.approvals scope and the approval runtime token; resolveApprovalOverGateway owns that connection path.
  • Missing detection / guardrail: tests asserted raw Gateway method calls instead of verifying that /approve uses the approval resolver path used by native approval actions.
  • Contributing context (if known): exec and plugin approvals both resolve through the shared approval visibility contract; plugin ids additionally need plugin.approval.resolve routing.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/auto-reply/reply/commands-approve.test.ts
  • Scenario the test should lock in: /approve calls resolveApprovalOverGateway instead of callGateway, preserves exec approval submission, routes plugin:<id> submissions to plugin approval resolution, and still enforces configured approvers across the shared command path.
  • Why this is the smallest reliable guardrail: the regression was in shared command routing, before any live channel delivery behavior.
  • Existing test that already covers this (if any): existing approve-command tests covered parsing/auth and are updated to assert the resolver path.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Manual /approve commands now submit decisions through the same approval resolver path used by trusted approval clients, so pending exec/plugin approvals can be resolved from channel command surfaces as documented.

Diagram (if applicable)

Before:
/channel /approve <id> -> callGateway -> ordinary transient gateway call -> approval may be hidden -> unknown/expired

After:
/channel /approve <id> -> resolveApprovalOverGateway -> operator approvals client -> approval manager -> resolved

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): No
  • If any Yes, explain risk + mitigation: N/A.

Repro + Verification

Environment

  • OS: Darwin 25.2.0 arm64
  • Runtime/container: local worktree, Node v24.15.0, pnpm 11.1.0
  • Model/provider: N/A
  • Integration/channel (if any): shared manual /approve command path; verified with a Slack command context and focused command-handler tests
  • Relevant config (redacted): channel approval approver config exercised in tests

Steps

  1. Submit /approve <id> <decision> from a configured approver.
  2. Handler authorizes the sender for that approval family.
  3. Handler calls resolveApprovalOverGateway to submit the decision through the operator approvals client path.

Expected

  • Pending exec/plugin approval resolves or the actual resolver error is returned.

Actual

  • Focused tests pass and assert approval routing through the resolver.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: real local Gateway proof for channel-context /approve plugin:<id> allow-once, real local Gateway proof for exec /approve <id> allow-once, focused approve-command test suite with exec approvals, plugin approvals, fallback routing, authorization, and gateway-client scopes.
  • Edge cases checked: unauthorized plugin approvers remain rejected in tests; legacy unprefixed fallback still attempts exec then plugin in tests; plugin-only not-found errors surface cleanly in tests.
  • What you did not verify: actual Slack network callback from the hosted Slack app in this split worktree.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: changing the manual command path could regress exec approval submission.
    • Mitigation: focused tests assert exec approval routing still submits successfully and preserve existing fallback behavior.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation size: S maintainer Maintainer-authored PR labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Workflow note: Future ClawSweeper reviews update this same comment in place.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Summary
The PR routes shared manual /approve handling through resolveApprovalOverGateway, updates focused exec/plugin approval tests, and aligns slash-command docs plus changelog wording.

Reproducibility: yes. source-level: current main sends manual approvals through callGateway, while approval lookup gives broad pending-record visibility only to trusted approval-runtime clients. The related report at #84456 provides matching user logs; I did not run a hosted channel repro in this read-only pass.

PR rating
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Summary: Strong focused PR with live Gateway proof, targeted regression coverage, docs alignment, and no blocking correctness findings.

What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Real behavior proof
Sufficient (live_output): The PR body includes copied live Gateway output showing after-fix plugin and exec approvals resolving from a Slack command context, plus focused command-handler test output.

Mantis proof suggestion
A Slack smoke would add transport-level confidence for the only untested hosted ingress path while the source fix sits downstream of slash-command delivery. A maintainer can ask Mantis to capture proof by posting a new PR comment that starts with the OpenClaw Mantis account mention, followed by:

slack desktop smoke: verify /approve plugin:<id> allow-once resolves a pending Slack plugin approval and /approve <exec-id> allow-once still submits.

Risk before merge

  • The shared manual /approve path now opens a trusted operator approvals client after sender authorization and gateway-scope checks; the PR proves a local Slack command context and focused tests, but not hosted Slack, WhatsApp, or every channel ingress.
  • The diff touches the approval-runtime token/scope boundary, so maintainers should explicitly accept that channel command authorization remains the guard before using the trusted resolver path.

Maintainer options:

  1. Accept Resolver Routing After Normal Gates (recommended)
    Treat the local Gateway proof and focused tests as sufficient for the shared command path, then merge once maintainers accept the cross-channel behavior change and required CI is green.
  2. Request A Hosted Slack Smoke
    Ask for a redacted hosted Slack smoke if maintainers want proof of the only untested ingress path before accepting the downstream resolver fix.
  3. Pause For Approval Boundary Review
    Pause the PR if maintainers want a broader review of which channel command surfaces may invoke the internal approval-runtime client.

Next step before merge
Protected labeling and the approval-runtime compatibility/auth tradeoff require maintainer acceptance; there is no narrow automated repair to queue from this review.

Security
Cleared: No new dependency, workflow, or permission expansion was found; the auth-sensitive resolver call remains behind existing sender authorization and gateway-scope checks.

Review details

Best possible solution:

Land the resolver-based implementation after maintainer acceptance of the approval-runtime compatibility tradeoff, preserving sender authorization and gateway-scope checks before trusted approval submission.

Do we have a high-confidence way to reproduce the issue?

Yes, source-level: current main sends manual approvals through callGateway, while approval lookup gives broad pending-record visibility only to trusted approval-runtime clients. The related report at #84456 provides matching user logs; I did not run a hosted channel repro in this read-only pass.

Is this the best way to solve the issue?

Yes: routing through resolveApprovalOverGateway after existing sender authorization and gateway-scope checks reuses the established approval-runtime boundary instead of duplicating approval visibility logic in the command handler.

Label justifications:

  • P2: This is a focused manual approval routing bug fix with limited but real channel-workflow impact.
  • merge-risk: 🚨 compatibility: The shared /approve path changes behavior across channel surfaces from raw gateway calls to the approval-runtime resolver.
  • merge-risk: 🚨 auth-provider: The change depends on the trusted operator approvals client, approval runtime token, and gateway scope checks for auth-sensitive approval resolution.
  • rating: 🦞 diamond lobster: Current PR rating is 🦞 diamond lobster because proof is 🦞 diamond lobster, patch quality is 🦞 diamond lobster, and Strong focused PR with live Gateway proof, targeted regression coverage, docs alignment, and no blocking correctness findings.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes copied live Gateway output showing after-fix plugin and exec approvals resolving from a Slack command context, plus focused command-handler test output.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live Gateway output showing after-fix plugin and exec approvals resolving from a Slack command context, plus focused command-handler test output.

What I checked:

  • Current main manual approval path: Current main parses and authorizes /approve, then submits decisions through raw callGateway with a backend gateway client instead of the operator approval-runtime resolver. (src/auto-reply/reply/commands-approve.ts:193, 4d47f9a4c038)
  • Approval visibility contract: Pending approval lookup grants broad visibility to operator.approvals clients only when the connected client is marked as the internal approval runtime; otherwise visibility can be tied to device, connection, or client identity. (src/gateway/server-methods/approval-shared.ts:85, 4d47f9a4c038)
  • Existing resolver contract: resolveApprovalOverGateway opens the operator approvals gateway client and routes exec, prefixed plugin, explicit plugin, and optional plugin fallback resolution through the gateway request API. (src/infra/approval-gateway-resolver.ts:17, 4d47f9a4c038)
  • Trusted approval runtime client: The operator approvals client supplies operator.approvals scope and the approval runtime token, which the gateway handshake recognizes as the trusted approval runtime. (src/gateway/operator-approvals-client.ts:28, 4d47f9a4c038)
  • PR diff uses the resolver: The PR replaces the raw gateway call in the shared command handler with resolveApprovalOverGateway, passing config, approval id, decision, sender id, display name, and explicit plugin routing for plugin resolution. (src/auto-reply/reply/commands-approve.ts:190, 7d23a5cbddd5)
  • Focused tests updated: The PR updates the approve-command tests to assert resolver calls for exec approvals, plugin approvals, authorization rejection, scope gates, and legacy unprefixed plugin fallback. (src/auto-reply/reply/commands-approve.test.ts:482, 7d23a5cbddd5)

Likely related people:

  • pgondhi987: GitHub file history shows recent merged work on command gateway scopes and approval visibility, including Enforce gateway command scopes by caller context and Bind gateway approval access to requester metadata, which are central to this bug shape. (role: recent approval access contributor; confidence: high; commits: 652f5f9b1087, 386d321634b3, a4da627da10b; files: src/auto-reply/reply/commands-approve.ts, src/gateway/server-methods/approval-shared.ts, extensions/slack/src/monitor/events/interactions.block-actions.ts)
  • steipete: GitHub file history shows repeated recent work on the operator approval gateway lifecycle, approval resolver exports, and gateway approval behavior adjacent to the resolver this PR reuses. (role: approval resolver and gateway lifecycle contributor; confidence: high; commits: 4ea80632036c, 566cbb24aa0d, 279e6453fc51; files: src/infra/approval-gateway-resolver.ts, src/gateway/operator-approvals-client.ts, src/gateway/server-methods/approval-shared.ts)
  • vincentkoc: GitHub file history for the shared command handler includes fix(runtime): split approval and gateway client seams, making this person relevant to the boundary between approval handling and gateway clients. (role: approval/gateway seam contributor; confidence: medium; commits: 0f7d9c957093; files: src/auto-reply/reply/commands-approve.ts, src/infra/approval-gateway-resolver.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 4d47f9a4c038.

@kevinslin kevinslin force-pushed the codex/slack-approve-runtime-fix branch from 5a7677e to 44413c7 Compare May 20, 2026 19:34
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🌱 uncommon Velvet Clawlet

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🌱 uncommon.
Trait: collects tiny proofs.
Image traits: location diff observatory; accessory release bell; palette cobalt, lime, and pearl; mood celebratory; pose peeking out from the egg shell; shell polished stone shell; lighting bright celebratory glints; background tiny shells and proof notes.
Share on X: post this hatch
Copy: My PR egg hatched a 🌱 uncommon Velvet Clawlet in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@kevinslin kevinslin force-pushed the codex/slack-approve-runtime-fix branch from 44413c7 to c5cd6a8 Compare May 20, 2026 20:22
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels May 20, 2026
@kevinslin kevinslin changed the title fix(slack): resolve manual plugin approvals through runtime fix(approval): resolve /approve plugin ids through approvals client May 20, 2026
@kevinslin kevinslin force-pushed the codex/slack-approve-runtime-fix branch from c5cd6a8 to 2986a6a Compare May 20, 2026 20:43
@kevinslin kevinslin changed the title fix(approval): resolve /approve plugin ids through approvals client fix(approval): route /approve through approval resolver May 20, 2026
@kevinslin kevinslin force-pushed the codex/slack-approve-runtime-fix branch 2 times, most recently from 1aa3bf9 to 8e00da6 Compare May 20, 2026 21:25
@kevinslin kevinslin requested a review from a team as a code owner May 20, 2026 21:25
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. label May 20, 2026
@kevinslin kevinslin force-pushed the codex/slack-approve-runtime-fix branch from 8e00da6 to 7d23a5c Compare May 20, 2026 22:42
@kevinslin kevinslin merged commit b58572e into main May 20, 2026
107 of 112 checks passed
@kevinslin kevinslin deleted the codex/slack-approve-runtime-fix branch May 20, 2026 23:00
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 24, 2026
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 24, 2026
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 24, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
galiniliev pushed a commit to galiniliev/openclaw that referenced this pull request May 25, 2026
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
SebTardif pushed a commit to SebTardif/openclaw that referenced this pull request May 26, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation maintainer Maintainer-authored PR merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants