Configuration
Credential store
Resolution order: --store <path> > $MAT_STORE > $XDG_CONFIG_HOME/mat >
~/.config/mat. It holds the Root CA, the controller’s keys/cert, the
commissioned-node ledger (nodes.json), the optional alias map
(aliases.toml, below), and the persistent Matter KVS (chip-tool-compatible
INI form — group keysets, operational credentials, the group-send counter;
mat is its sole reader/writer). It is never committed (excluded by
.gitignore).
The store holds the Root CA private key and the IPK in plaintext, so mat
writes every store file owner-only (0600, regardless of umask — each write is
a tmp+rename replace, so the mode is re-applied every time) and creates a
missing store directory as 0700. An existing directory’s mode is left as is
(e.g. a mounted Docker volume) — make sure it is not world-readable.
matd socket
matd binds $XDG_RUNTIME_DIR/matd/matd.sock by default (directory created
0700) and mat probes that path (then the pre-0.27.0
$XDG_RUNTIME_DIR/matd.sock). Without a usable XDG_RUNTIME_DIR (unset,
empty, or relative) there is no default — the old /tmp/matd.sock
fallback was removed because another local user could pre-create a fake socket
in the shared /tmp. Then matd needs --socket <path>, and mat finds no
daemon unless given --matd <path> / MAT_MATD_SOCKET (auto-detect simply
uses the direct path). The socket is set to 0600 after bind and
<socket>.lock is created 0600: the file mode is the only access control,
so put an explicit socket in a directory only that user can write to. See
Routing through matd.
Aliases (aliases.toml, optional)
Numeric node / group / endpoint ids are easy to get wrong. If the file
<store>/aliases.toml exists, mat resolves locally defined names to those
numbers right after arg parsing — a purely local convenience. Without the
file, behavior is exactly the traditional numeric-only one. The wire and the
backend (native engine / matd) always receive numbers, and stdout keeps the
numeric schema (no alias echo-back).
version = 1
[nodes]
living-light = 5
hall-sensor = 12
[groups]
all-lights = 258
[endpoints.living-light]
main = 1
night = 2
[endpoints.12]
pir = 3
[colors]
warm = "#ff8c00"
mypink = "255,182,193"
[thread]
"AABBCCDDEEFF0011" = "otbr-br"
nodes: alias → node_id. Accepted by-n/--node(read / write / invoke / describe / on / off / color-temp / color / level / open-window / diag thread / diag node) and by--nodesingroup provision/diag mesh(each element resolved independently).groups: alias → GroupId. Accepted by-g/--groupingroup provision/invoke/grant/color-temp/color/level(bumptakes none — the counter is fabric-global).endpoints: defined per node — the outer key is a node alias or a node_id digit string, the inner map is alias → endpoint number (endpoint numbers mean different things on different nodes, so there is no global endpoint dictionary). Accepted by-e/--endpointon node-taking commands; the lookup uses the resolved node, so-n 5 -e mainand-n living-light -e maingive the same result. The-eofgroup provision/group invoke/group color-temp/group color/group levelis numeric only (no node context to resolve against).colors: custom color name → RGB value (#rrggbb/rrggbb/R,G,B), used by--nameincolor/group color. Entries are defined as RGB and go through the same RGB → HSV pipeline as--rgb. A user-defined name overrides the built-in color table (you can redefinered). Without the file the built-in table still works. A value that does not parse as RGB isstore_parse(exit10); an unknown color name is a CLI argument error (exit2) listing the known names.thread: Thread ExtAddress (16 hex, case-insensitive) → display label, used bymat diag meshto name unknown participants (OTBR border routers, other- fabric devices) that show up in a neighbor/route table but were never commissioned onto this fabric, so they have nonodesalias to fall back on. The graph’slabelfield matches on ExtAddress regardless of fabric status, so a commissioned node whose ExtAddress happens to be listed here gets alabeltoo, alongside itsnodesalias.
# With the aliases.toml above, these are equivalent:
mat on -n living-light
mat on -n 5
Resolution rules:
- A value that parses as a number is used as-is (numbers win; full backward
compatibility). Only non-numeric values are looked up in
aliases.toml. - Alias names must be non-empty and not all digits (this shadowing is rejected
when the file is loaded:
store_parse, exit10). - An unknown alias — or any alias given when there is no
aliases.tomlin the store — is a CLI argument error (exit2); the stderrdetaillists the known aliases (or saysno aliases.toml in store) so the caller can self-correct. - A corrupt
aliases.tomlisstore_parse(exit10). - Cluster / attribute / command names are never aliased (chip-tool notation only).
These map onto the existing exit codes (2 / 10); the
Errors and exit codes table is unchanged.
To register an alias while commissioning, add --alias:
mat commission --setup-code "MT:Y.K9042C00KA0648G00" --node 5 --alias living-light
The name is validated before commissioning starts (all-digits / empty /
already taken → exit 2, before any network op runs), and it is written
to aliases.toml only on success (the file is created if absent). Without
--alias, commission never touches aliases.toml. Deleting or renaming an
alias is a hand edit of the file — there is no management subcommand.
Subscriptions (subscriptions.toml, optional, matd only)
By default matd‘s resident Subscribe (see Resident Subscribe and mat listen) is a full wildcard: every
endpoint/cluster/attribute, on every commissioned node. If
<store>/subscriptions.toml exists, matd narrows that to just the listed
clusters’ paths instead.
Full-wildcard priming (the initial full-attribute dump right after a
subscription is (re)established — dozens of request/response round trips) can
fail to complete on a weak Thread link, leaving a subscription unestablished
for tens of minutes to hours. Narrowing to a handful of clusters shrinks
priming to one or two chunks, so a link good enough for read is usually
good enough to subscribe on too.
clusters = [
"onoff",
"occupancysensing",
"temperaturemeasurement",
]
- Cluster names use chip-tool notation (same as
mat read); numeric ids ("0x0006"/"6") also work, the same escape hatch as elsewhere for namesmat-core::idsdoesn’t know. - Absent file = full wildcard, unchanged — the same absent-file discipline
as
aliases.toml. - A parse failure, an unknown cluster name, or an empty list makes
matdrefuse to start (store_parse, exit10); it never silently falls back to wildcard, so a misconfiguration can’t quietly disable the weak-link workaround. - Edge case: nodes that serve none of the listed clusters. When the file is present, the narrowed Subscribe is sent to every commissioned node; a node that exposes none of the listed clusters will never establish its subscription (it retries on backoff forever). Ensure each node serves at least one of the listed clusters.
- When this file is present,
mat listen’s attribute lines only ever come from the listed clusters — a--clusterfilter naming a cluster outside that set simply never matches anything on the attribute side. (This narrows attribute paths only; theeventskey below narrows event paths independently — see there for what “no event lines outside the list” means on the event side.) - Read once at
matdstartup; an edit needs amatdrestart to take effect (e.g.systemctl --user restart matd). mat(one-shot) never reads this file — like the rest of the resident Subscribe, it is matd-only.
events — event subscription scope
events is independent of clusters above: clusters narrows the
attribute paths of the resident Subscribe, events controls which
clusters’ events (EventRequests) it asks for. Either key can be present
without the other, and a file with only one of them is valid.
# All three forms are independent of `clusters`.
events = ["switch", "booleanstate"] # only these clusters' events
events = [] # no EventRequests at all
# events key omitted entirely -> all clusters' events (wildcard)
- Key absent — every cluster’s events, urgent priority (the same
wildcard discipline as an absent
subscriptions.toml). events = [...]— only the listed clusters’ events, urgent priority. Same name/numeric resolution asclusters(chip-tool notation or"0x0006"/"6", unknown names arestore_parse).events = []— noEventRequestsat all: the resident Subscribe goes out exactly as it did before event subscription support (Phase A), i.e. attribute reports only.- All event paths are sent
IsUrgent = trueregardless of which of the three forms is in effect — there is no non-urgent option (see Resident Subscribe andmat listenfor why). - Rollout / canary. Order matters: update the
mat listenconsumers first, then enableevents. Even the narrow canary list putsbooleanstatestate-changelines (andswitchpresses) into every unfilteredmat listenstream, and those lines count against--count, so a consumer doingmat listen --count 1can get an event line where it used to get an attribute line. Give each consumer either a branch on theattribute/eventkey or an explicit--attributeflag before turning the key on. Then start productionmatdwithevents = ["switch", "booleanstate"]rather than jumping straight to wildcard: watch priming time across all nodes and stderr for any device rejecting theEventRequestspath —matdlogs onewarnper report withrejected(count) plus the first rejected endpoint/cluster/event/status, andINVALID_ACTIONduring establish/priming — then widen by removing theeventskey entirely once that looks clean.events = []is the instant off switch if the widen step goes wrong. - Upgrade caveat — absent means wildcard. Upgrading
matdto a phase-B-or-later build with an existingsubscriptions.tomlthat has noeventskey turns on wildcard event subscriptions for every commissioned node the momentmatdrestarts (the same absent-file default asclusters, but new behavior the day this key first exists). If that is not what you want on day one, add an explicitevents = [...](orevents = []) line tosubscriptions.tomlbefore deploying the upgrade, following the canary path above.