Relay permission prompts through the Matrix channel #109
Labels
No labels
blocked
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
waiting-on-julian
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/matrix-mcp#109
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The channel server declares
claude/channeland nothing else, so a permissionprompt in any session using it can only be answered at the terminal. Julian is
driving the fleet from Matrix on a phone, so every gated tool call stalls until
he reaches a keyboard.
Claude Code's channel contract has a relay capability for exactly this. Docs:
https://code.claude.com/docs/en/channels-reference#relay-permission-prompts
Three pieces:
Declare
claude/channel/permission: {}alongsideclaude/channelunderexperimentalcapabilities. Todaysrc/channel.rs:166has onlyCHANNEL_CAPABILITY = "claude/channel".Handle
notifications/claude/channel/permission_request. Params are fourstrings:
request_id(five lowercase letters from a-z withoutl),tool_name,description,input_preview. Format them into the outgoingMatrix message and include the id verbatim — the terminal dialog never shows
it, so the outbound message is the only place it can be learned.
In the inbound handler, before forwarding text as chat, match
^\s*(y|yes|n|no)\s+([a-km-z]{5})\s*$case-insensitively, lowercase thecaptured id, and emit
notifications/claude/channel/permissionwith{request_id, behavior: "allow"|"deny"}. Return without also forwarding itas a message.
Both dialogs stay live; Claude Code applies whichever verdict arrives first and
drops the other. A verdict for an unknown id is dropped silently.
Scope, so it is not oversold: relay covers tool-use approvals (Bash, Write,
Edit). Project-trust and MCP-consent dialogs never relay.
AskUserQuestionselections and plan-mode approval are not part of this protocol either — those
are Remote Control's job, tracked separately.
Security
The docs are explicit that this should only be declared by a channel that
authenticates the sender, because anyone who can reply through the channel can
approve tool use in the session. Our inbound path is already sender-gated;
confirm that gate runs before the verdict branch, not after, and add a test that
a non-allowlisted sender's
yes <id>is dropped rather than applied.Acceptance
Not "the capability is declared". A negative control, per AGENTS.md:
channel, answer
no <id>from Matrix, and watch the tool call be rejected.yes <id>and watch it run.dropped while the terminal dialog stays open.
yes <id>from a sender not on the allowlist and confirm nothinghappens.
Client requirement is Claude Code >= 2.1.234; the fleet is on 2.1.245.
jlxq0 referenced this issue2026-08-25 07:11:44 +00:00
Release state for
v0.10.5, recorded here because the condition was addressed to a session and sessions end.The tag is
v0.10.5on21d74338ad39806945373a589b732a02d6efddf3, which is the #115 merge, notorigin/main—origin/mainhas since moved to docs-only #121, anddockerhasneeds: cargo, so tagging an unpassed commit spends a version for nothing.Verified and unchanged as of this comment:
CI / cargoon21d74338: success, 1m36s, 11:05:03Z.CI / docker: success, 3m16s, 11:08:22Z.240 passed; 0 failed, audit clean over 498 dependencies with 8 pre-existing allowed warnings,deny check bans licenses sourcesok.v0.10.4..21d74338is four commits, all #115:6c0abf2,58fc55b,02ff731,21d74338.v0.10.5on the remote; tags top out atv0.10.4→e15a350.21d74338is an ancestor oforigin/main.v0.10.4atsha256:c1e102f4….The condition. Clark, holding the release gate, put the authorisation question to Julian in writing. He engaged and chose the resolution: one line direct to the worker, not a relay. The worker it was addressed to died with its window before the line arrived, so the hold has outlived its addressee. Nothing about the release is in question and has not been for hours; what is outstanding is a word from Julian, and only from Julian — a relay of his answer by any director or peer is a peer message however faithful, and is not it.
Why this is written down rather than said. A hold whose release is addressed to a session is unreleasable by construction once that session ends, and the only moves left are overriding it or asking the principal twice. A release condition belongs somewhere still there when the reader is not. Whoever picks this up next inherits the state from this comment instead of rediscovering it.
The annotation to tag with is drafted, reviewed and approved; it follows the v0.10.2–v0.10.4 convention of the lead commit's subject then prose on what broke and why.
Tagged.
v0.10.5is on the remote at21d74338ad39806945373a589b732a02d6efddf3, tag object1348f8e. Clark cut it, after Julian sent the go to Clark in Matrix rather than into the worker's window; re-cutting the ask a fourth time would have spent his attention on a boundary already resolved twice, so Clark took the act itself.CI / cargofor the tag ref is task17341, created 09:15:59Z.One defect in the annotation, recorded because the tag is the permanent record. The pushed message is missing one line in the third paragraph, between "so Some was not joined, the" and "dialogs still open, because live_peers counts without evicting":
git tag -adefaults to--cleanup=strip, which removes commentary lines, and a line beginning with#is one. Measured with a control on a throwaway local tag at the same sha: the same message keeps that line under--cleanup=verbatimand loses it under the default.#107survives in the same annotation because it sits mid-line;#113did not, because the paragraph wrapped so that it started one. Nothing in git's output says a line was dropped.The consequence is that the shipped release note breaks mid-clause and two of the four review fixes are unnamed in it: the
#113trap, and the half of thelive_peersearly return that says what it did.Whether to re-cut is the gate holder's call and costs a cancel and requeue on a capacity-1 queue. The commit and therefore the image are identical either way.
The pitfall for
AGENTS.md: a tag annotation that cites an issue by number loses that line silently whenever the wrap puts the#first, and an annotation is exactly where issue numbers belong. Either pass--cleanup=verbatimor reflow so no line starts with#; the reflow is the version that survives someone later dropping the flag.Shipped in
v0.10.5.6c0abf2implemented the relay,58fc55bclosed four holes found in review,02ff731made replay's judgement reachable, and the merge is21d7433./channeldeclaresclaude/channel/permission, a prompt is rendered into a Matrix message carrying the five-letter request id verbatim, and a reply ofyes <id>orno <id>answers it.Closing on a stale-issue sweep. The tag containing the work is
v0.10.5and it has been running since; the issue stayed open through three further releases.