Skip to content

[codex] Fail closed pair slash command routing#98262

Merged
steipete merged 12 commits into
openclaw:mainfrom
brokemac79:codex/pair-qr-bind-fail-closed
Jul 6, 2026
Merged

[codex] Fail closed pair slash command routing#98262
steipete merged 12 commits into
openclaw:mainfrom
brokemac79:codex/pair-qr-bind-fail-closed

Conversation

@brokemac79

@brokemac79 brokemac79 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes #98239.

The Control UI sends chat text literally through chat.send. A spaced plugin slash command such as / pair qr did not match the active /pair registration, so it could continue through the normal agent path. In the reported Tailscale Serve setup, that exposed an availability risk because an agent turn could try to repair configuration and move gateway.bind away from the loopback backend used by Serve.

Why This Change Was Made

This keeps the fix at the registered plugin-command boundary:

  • the canonical plugin matcher accepts whitespace after /, including / pair qr
  • authorization probing recognizes spaced syntax only when the command is present in the active runtime registry
  • mention-prefixed group text receives the same authorization treatment as canonical inline slash commands
  • inactive or denied plugins do not reserve aliases
  • plugin-before-built-in ordering, built-in reservations, command scopes, channel restrictions, and transport-neutral dispatch remain unchanged

The earlier manifest-level unavailable-handler fallback was removed because it would have established a new fail-closed policy for every manifest-declared runtime slash alias. This PR does not make that product-policy change.

User Impact

When device-pair is active, / pair qr is handled by the same authorized, LLM-free device-pair path as /pair qr; it cannot fall through into an agent turn. If device-pair is inactive or denied, the text keeps the existing inactive-plugin behavior rather than claiming a new command reservation.

Evidence

Exact candidate: bcfa481fbefb8895421d280cb977f71848d8d808.

  • Blacksmith Testbox tbx_01kwe8v2agj1mes7fjajb7b9be: focused command/plugin suite passed, 4 files / 139 tests.
  • Same Testbox: Control UI Chromium E2E passed, 16 tests; the composer sent / pair qr unchanged as chat.send input.
  • Same Testbox, exact candidate: full build passed, a real headless Chromium browser loaded the Gateway-hosted Control UI, sent / pair qr, observed the device-pair QR response, and read back gateway.bind=loopback. Gateway logs showed device-pair: /pair invoked ... action=qr and no agent run.
  • Same Testbox, exact candidate: pnpm check:changed passed, including core/core-test typechecks, changed-file lint, import-cycle check, pairing guards, storage guards, and changed-surface policy checks.
  • Same Testbox: pnpm check:import-cycles reported 0 runtime value cycles.
  • Targeted oxfmt --check passed for all five changed files.
  • Fresh autoreview after repairs: no accepted/actionable findings; overall patch correct at 0.96 confidence.
  • Tailscale was not installed on the Testbox, so no new Serve identity mutation was attempted. The contributor's earlier production-VPS proof on head 434a7f18 exercised Tailscale Serve with a loopback backend and observed gateway.bind=loopback, HTTPS root 200, and HTTPS health 200 after both /pair qr and / pair qr; production state was restored afterward.

No CHANGELOG.md change.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling extensions: device-pair size: M triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 30, 2026
@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 5, 2026, 9:39 PM ET / 01:39 UTC.

Summary
The PR changes plugin command matching and command authorization detection so spaced slash commands like / pair qr route to registered plugin commands, with focused regression coverage.

PR surface: Source +15, Tests +53. Total +68 across 5 files.

Reproducibility: yes. at source level: current main parses / pair qr as command name /, so the registered /pair command is missed and normal agent fallback can proceed. I did not re-run the full Tailscale Serve environment in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98239
Summary: This PR is the candidate fix for the open Tailscale Serve /pair qr fallthrough issue.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Wait for exact-head QA Smoke CI to complete before merge.

Risk before merge

  • [P1] Exact-head CI still had QA Smoke CI in progress at review time; wait for that job before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land this narrow command-boundary fix after exact-head QA finishes, keeping spaced slash routing tied to active registered plugin commands.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] No repair job is needed because the PR already contains the focused fix and sufficient proof; the remaining action is normal maintainer landing after exact-head QA completes.

Security
Cleared: No concrete security or supply-chain regression was found; the final diff only changes command parsing/tests, and plugin command execution still enforces authorization and required scopes.

Review details

Best possible solution:

Land this narrow command-boundary fix after exact-head QA finishes, keeping spaced slash routing tied to active registered plugin commands.

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

Yes, at source level: current main parses / pair qr as command name /, so the registered /pair command is missed and normal agent fallback can proceed. I did not re-run the full Tailscale Serve environment in this read-only review.

Is this the best way to solve the issue?

Yes: fixing the registered plugin command matcher and authorization trigger is the narrow owner boundary shared by Control UI and text transports. A UI rewrite or broad manifest-level fail-closed policy would be wider and less maintainable.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 4f8eeeaca9a9.

Label changes

Label justifications:

  • P1: The PR fixes a high-priority Control UI pairing path where a spaced slash command could fall through to an agent turn and break the normal Tailscale Serve webchat route.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides after-fix Testbox browser/Gateway proof and production Tailscale Serve proof showing / pair qr reaches device-pair without an agent run and keeps gateway.bind=loopback.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix Testbox browser/Gateway proof and production Tailscale Serve proof showing / pair qr reaches device-pair without an agent run and keeps gateway.bind=loopback.
Evidence reviewed

PR surface:

Source +15, Tests +53. Total +68 across 5 files.

View PR surface stats
Area Files Added Removed Net
Source 2 20 5 +15
Tests 3 55 2 +53
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 5 75 7 +68

What I checked:

  • Current-main command miss: Current main splits plugin command text on the first space, so / pair qr produces command name / rather than matching the registered /pair plugin command. (src/plugins/commands.ts:76, 4f8eeeaca9a9)
  • PR command matcher: The PR head parses optional whitespace after / and reconstructs the command as /${commandMatch[1]}, keeping / pair qr attached to /pair while preserving argument parsing. (src/plugins/commands.ts:76, 47269ccac314)
  • PR authorization trigger: The PR adds a spaced-plugin-command probe that computes command authorization only when the spaced form matches an active registered plugin command. (src/auto-reply/command-detection.ts:94, 47269ccac314)
  • Plugin execution boundary: Plugin commands are handled before built-in commands and agent fallback, and execution still enforces authorization, channel support, and required gateway scopes before calling the plugin handler. (src/plugins/commands.ts:257, 4f8eeeaca9a9)
  • Device-pair scope boundary: The device-pair plugin registers /pair with operator.pairing, so the PR routes the spaced command into the existing scoped, LLM-free command path rather than adding a new policy surface. (extensions/device-pair/index.ts:672, 4f8eeeaca9a9)
  • Regression coverage: The PR adds focused tests for spaced plugin command matching, authorization detection, inline-token behavior, and Control UI sending / pair qr unchanged through chat.send. (src/auto-reply/command-control.test.ts:1164, 47269ccac314)

Likely related people:

  • steipete: Current-main blame for the command matcher and device-pair lines points to Peter Steinberger, and the latest PR commits plus land-ready comment show he carried the final command-boundary implementation and review. (role: recent area contributor and reviewer; confidence: high; commits: 37c67b0d27b3, 5ea4de10d33f, 47269ccac314; files: src/plugins/commands.ts, src/auto-reply/command-detection.ts, extensions/device-pair/index.ts)
  • Glucksberg: Git history shows the LLM-free plugin command API was introduced in commit 4ee808d, which owns the dispatch surface this PR adjusts. (role: command API introducer; confidence: medium; commits: 4ee808dbcb74; files: src/plugins/commands.ts, src/auto-reply/reply/commands-plugin.ts)
  • Mariano Belinky: Git history shows the device-pair plugin and /pair command surface came from the device pairing and phone control plugin introduction. (role: device-pair feature introducer; confidence: medium; commits: 730f86dd5c43; files: extensions/device-pair/index.ts)
  • Vincent Koc: Recent command-surface history includes command listing and detection refactors relevant to authorization detection. (role: recent command-surface contributor; confidence: medium; commits: 1f1b50498049; files: src/auto-reply/command-detection.ts, src/plugins/commands.ts)
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.

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.
Review history (2 earlier review cycles)
  • reviewed 2026-07-06T00:34:20.124Z sha 24ac12f :: needs changes before merge. :: [P3] Remove the release-owned changelog entry
  • reviewed 2026-07-06T01:07:04.647Z sha 96587cb :: needs maintainer review before merge. :: none

@brokemac79
brokemac79 marked this pull request as ready for review June 30, 2026 19:59
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jun 30, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jun 30, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 30, 2026
@brokemac79

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot added triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. and removed triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jun 30, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 30, 2026
@steipete steipete self-assigned this Jul 1, 2026
@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: S and removed agents Agent runtime and tooling extensions: device-pair size: M labels Jul 1, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: S and removed channel: mattermost Channel integration: mattermost channel: slack Channel integration: slack channel: telegram Channel integration: telegram app: macos App: macos gateway Gateway runtime scripts Repository scripts agents Agent runtime and tooling size: XL labels Jul 6, 2026
@steipete
steipete force-pushed the codex/pair-qr-bind-fail-closed branch from 47269cc to 39f03a0 Compare July 6, 2026 01:42
@steipete
steipete merged commit 49ae7ec into openclaw:main Jul 6, 2026
98 checks passed
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
* fix: keep pair qr from widening gateway bind

* fix: honor plugin activation for slash reservations

* fix: authorize spaced slash commands

* fix: keep reserved commands out of manifest reservations

* fix: avoid manifest fail-closed for declined commands

* fix: gate manifest command fallback by auth

* fix: keep runtime command probe internal

* fix: scope spaced slash authorization

* fix: route spaced plugin slash commands

* docs: note spaced plugin command routing

* docs: note spaced plugin command routing

* docs: split command routing changelog follow-up

---------

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
* fix: keep pair qr from widening gateway bind

* fix: honor plugin activation for slash reservations

* fix: authorize spaced slash commands

* fix: keep reserved commands out of manifest reservations

* fix: avoid manifest fail-closed for declined commands

* fix: gate manifest command fallback by auth

* fix: keep runtime command probe internal

* fix: scope spaced slash authorization

* fix: route spaced plugin slash commands

* docs: note spaced plugin command routing

* docs: note spaced plugin command routing

* docs: split command routing changelog follow-up

---------

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
wheakerd pushed a commit to wheakerd/clawdbot that referenced this pull request Jul 15, 2026
* fix: keep pair qr from widening gateway bind

* fix: honor plugin activation for slash reservations

* fix: authorize spaced slash commands

* fix: keep reserved commands out of manifest reservations

* fix: avoid manifest fail-closed for declined commands

* fix: gate manifest command fallback by auth

* fix: keep runtime command probe internal

* fix: scope spaced slash authorization

* fix: route spaced plugin slash commands

* docs: note spaced plugin command routing

* docs: note spaced plugin command routing

* docs: split command routing changelog follow-up

---------

Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
Co-authored-by: Peter Steinberger <58493+steipete@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
(cherry picked from commit 49ae7ec)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary 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.

[Bug]: /pair qr can change gateway.bind and break Tailscale Serve webchat

2 participants