You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Matter supports a multi-admin model: a device commissioned into one fabric (e.g. ours) can be handed off to another controller (Apple Home, Google Home, SmartThings) without factory-resetting. The current Administrator re-opens a one-shot commissioning window on the already-commissioned Node, prints a QR / Manual Pairing Code, and the user enters it into the new controller.
Today matter-cli can only commission new devices — there is no command to open a fresh window on an existing node.
interaction.Client.InvokeTimed (internal/interaction/client.go:151) + daemon path via InvokeReq.TimedMs (internal/daemon/server.go:460)
QR / Manual Pairing Code generation from passcode + discriminator
commissioning.SetupPayload.QRCode / .ManualPairingCode (internal/commissioning/payload.go) — already used by matter code generate
Stored VID / PID per node (needed so the generated QR is accepted by commercial ecosystems)
store.Node.VendorID / .ProductID, populated by buildNodeFromResult in cli/commission.go:350
Cobra wiring pattern with @target + daemon-aware connect
cli/cluster.go:528invokeCommand
So the missing pieces are: a command, the PAKE verifier construction (w0 || L, 97 bytes), and the OCW-specific UX (random passcode, countdown, close/status).
Proposed CLI
# Open an Enhanced Commissioning Window on an already-commissioned node.# Default timeout 180s, random 27-bit passcode + 12-bit discriminator.
matter commission open-window @1
matter commission open-window @kitchen-light --timeout 5m
# Power-user flags (mostly for testing / reproducibility).
matter commission open-window @1 \
--passcode 20202021 \
--discriminator 3840 \
--iterations 10000 # default 1000; spec allows 1000..100000# Close an open window early.
matter commission close-window @1
# Basic Commissioning Method (only if device advertises Feature BC — rare).
matter commission open-window @1 --basic
Expected output (happy path)
✓ Reading BasicInformation (VID=0xFFF1, PID=0x8000)
✓ Opening commissioning window on node 1 (timeout: 3m0s, iterations: 1000)
✓ Commissioning window is open
QR Code: MT:Y3.13OTB00KA0648G00
Manual Code: 3497-011-2332
Passcode: 20202021
Discriminator: 3840
Expires: 15:42:17 (in 2m58s)
Pair this device into another ecosystem by scanning the QR code above,
or enter the manual code into the target controller (Apple Home, Google Home, …).
On close:
✓ Revoking commissioning window on node 1
✓ Window closed
Implementation Tasks
New file cli/commission_window.go with three subcommands under commission:
optional: window-status — trivial read of AdministratorCommissioning.WindowStatus / AdminFabricIndex / AdminVendorId on endpoint 0 (can be done with matter cluster read today, so may skip)
PAKE verifier helper — new internal/commissioning/verifier.go (or add to existing payload.go):
// ComputePAKEVerifier returns the 97-byte (w0 || L) verifier for OCW.funcComputePAKEVerifier(passcodeuint32, salt []byte, iterationsint) ([]byte, error)
Implementation: DeriveSPAKE2PValues → ComputeL(w1) → append(w0, L...). Add table-driven tests with vectors from connectedhomeip/src/crypto/tests/ (the SDK ships spake2p test vectors).
Random salt + passcode generators (crypto/rand):
Passcode: 27-bit uniform, reject the 12 invalid values listed in the spec (00000000, 11111111, 22222222, …, 12345678, 87654321).
Salt: 16 random bytes (spec constraint 16..32).
Discriminator: 12-bit random when not supplied.
Open-window command flow (runOpenWindow):
Resolve @target → node ID; require it (error if missing).
Load store.Node → pull VendorID / ProductID. If zero, fall back to reading BasicInformation cluster over CASE.
Generate passcode/salt/discriminator (or take from flags).
Timed Invoke on endpoint 0, cluster 0x003C, command 0x00, with timedMs equal to the IM-level timeout (spec recommends e.g. 10 * 1000 ms, independent of commissioning timeout). Prefer daemon path if running, else direct CASE.
Build a commissioning.SetupPayload from {VID, PID, passcode, discriminator, flow=Standard, DiscoveryCapabilities=OnNetwork} and call .QRCode() + .ManualPairingCode().
Print result + expiry time.
Close-window command flow (runCloseWindow):
Timed Invoke of RevokeCommissioning (command 0x02, no fields) on cluster 0x003C, endpoint 0. Map the WindowNotOpen cluster-specific status (0x04) to a friendly error.
Cluster-specific status decoding — the Admin Commissioning cluster returns cluster-specific status codes (0x02 Busy, 0x03 PAKEParameterError, 0x04 WindowNotOpen). Add a small mapper in the new file and surface a human message instead of raw 0x02.
Flags
--timeout duration (default 3m, range 3m..15m — spec: 180..900 s)
--iterations uint32 (default 1000, range 1000..100000)
--passcode uint32 (optional; random if unset)
--discriminator uint16 (optional; random if unset)
--salt-hex string (optional; 16 random bytes if unset)
--basic (use OpenBasicCommissioningWindow instead; no PAKE params)
Help / CLAUDE.md — add examples under "Core commands" in CLAUDE.md and refresh matter-cli help so the new subcommands are visible.
Tests
Unit: ComputePAKEVerifier against SDK vectors; flag parsing & validation; cluster-status mapping.
Integration (build-tag integration): against matter.js test device (there is a matter-js-test-device skill). Open → inspect WindowStatus attr == WindowOpen → close → WindowStatus == WindowNotOpen.
Acceptance Criteria
matter commission open-window @<node> prints a working QR + manual code and WindowStatus on the device transitions to WindowOpen.
A second controller (matter.js test device commissioner, chip-tool, or a real ecosystem) can pair against the node using the printed code, entering a second fabric.
matter commission close-window @<node> revokes an open window; WindowStatus returns to WindowNotOpen.
Calling open-window twice in a row returns a clean "commissioning window already open (Busy)" error, not a raw 0x02.
Timed-invoke path is used (non-timed invoke of OCW is rejected by the spec).
Works both standalone and with the session daemon running (-K 30m).
Documented in CLAUDE.md under Core commands and shown in matter commission --help.
Out of Scope / Follow-ups
JointFabricAdministrator cluster (0x0753) — separate cluster for joint-fabric handoff; not needed for the common "add to Apple Home" case. Track separately.
Rendering the QR as an actual scannable image/ANSI block in the terminal — issue #? (nice-to-have; printing the MT: string is sufficient for every major ecosystem app).
Context
Matter supports a multi-admin model: a device commissioned into one fabric (e.g. ours) can be handed off to another controller (Apple Home, Google Home, SmartThings) without factory-resetting. The current Administrator re-opens a one-shot commissioning window on the already-commissioned Node, prints a QR / Manual Pairing Code, and the user enters it into the new controller.
Today
matter-clican only commission new devices — there is no command to open a fresh window on an existing node.Spec references:
connectedhomeip-spec/src/multi_admin/multi_admin.adoc(Ch.12 Multiple Administrators)connectedhomeip-spec/src/service_device_management/AdminCommissioningCluster.adoc(cluster0x003C)connectedhomeip-spec/src/secure_channel/Password_Authenticated_Session_Establishment.adoc(PAKE verifier derivation)Current State in the Codebase
Most of the primitives already exist — this issue mostly wires them together:
AdministratorCommissioningcluster (0x003C) TLV types & command IDsinternal/clusters/administratorcommissioning/cluster.go(codegen)DeriveSPAKE2PValues(passcode, salt, iterations)→w0,w1internal/crypto/pbkdf2.goComputeL(w1)→ 65-byte uncompressed P-256 pointinternal/crypto/spake2p.gointeraction.Client.InvokeTimed(internal/interaction/client.go:151) + daemon path viaInvokeReq.TimedMs(internal/daemon/server.go:460)commissioning.SetupPayload.QRCode/.ManualPairingCode(internal/commissioning/payload.go) — already used bymatter code generatestore.Node.VendorID/.ProductID, populated bybuildNodeFromResultincli/commission.go:350@target+ daemon-aware connectcli/cluster.go:528invokeCommandSo the missing pieces are: a command, the PAKE verifier construction (
w0 || L, 97 bytes), and the OCW-specific UX (random passcode, countdown, close/status).Proposed CLI
Expected output (happy path)
On close:
Implementation Tasks
New file
cli/commission_window.gowith three subcommands undercommission:open-window(Enhanced Commissioning Method, command0x00)close-window(RevokeCommissioning, command0x02)window-status— trivial read ofAdministratorCommissioning.WindowStatus / AdminFabricIndex / AdminVendorIdon endpoint 0 (can be done withmatter cluster readtoday, so may skip)PAKE verifier helper — new
internal/commissioning/verifier.go(or add to existingpayload.go):Implementation:
DeriveSPAKE2PValues→ComputeL(w1)→append(w0, L...). Add table-driven tests with vectors fromconnectedhomeip/src/crypto/tests/(the SDK shipsspake2ptest vectors).Random salt + passcode generators (
crypto/rand):Open-window command flow (
runOpenWindow):@target→ node ID; require it (error if missing).store.Node→ pullVendorID/ProductID. If zero, fall back to readingBasicInformationcluster over CASE.administratorcommissioning.OpenCommissioningWindowRequest.0x003C, command0x00, withtimedMsequal to the IM-level timeout (spec recommends e.g.10 * 1000ms, independent of commissioning timeout). Prefer daemon path if running, else direct CASE.commissioning.SetupPayloadfrom{VID, PID, passcode, discriminator, flow=Standard, DiscoveryCapabilities=OnNetwork}and call.QRCode()+.ManualPairingCode().Close-window command flow (
runCloseWindow):RevokeCommissioning(command0x02, no fields) on cluster0x003C, endpoint 0. Map theWindowNotOpencluster-specific status (0x04) to a friendly error.Cluster-specific status decoding — the Admin Commissioning cluster returns cluster-specific status codes (
0x02 Busy,0x03 PAKEParameterError,0x04 WindowNotOpen). Add a small mapper in the new file and surface a human message instead of raw0x02.Flags
Help / CLAUDE.md — add examples under "Core commands" in
CLAUDE.mdand refreshmatter-clihelp so the new subcommands are visible.Tests
ComputePAKEVerifieragainst SDK vectors; flag parsing & validation; cluster-status mapping.integration): againstmatter.jstest device (there is amatter-js-test-deviceskill). Open → inspectWindowStatusattr ==WindowOpen→ close →WindowStatus==WindowNotOpen.Acceptance Criteria
matter commission open-window @<node>prints a working QR + manual code andWindowStatuson the device transitions toWindowOpen.matter commission close-window @<node>revokes an open window;WindowStatusreturns toWindowNotOpen.open-windowtwice in a row returns a clean "commissioning window already open (Busy)" error, not a raw0x02.-K 30m).CLAUDE.mdunder Core commands and shown inmatter commission --help.Out of Scope / Follow-ups
JointFabricAdministratorcluster (0x0753) — separate cluster for joint-fabric handoff; not needed for the common "add to Apple Home" case. Track separately.MT:string is sufficient for every major ecosystem app).matter decommission#46 (decommission).