Files
werkregie/doc/prs/2026-10-16-PR#93-matrix-control.md
claude-code ec7c982bf0
werkator build succeeded after 01:20
werkator/deploy build succeeded after 00:31
werkator/mutation build failed after 16:16
werkator/vulnerability-data build succeeded after 00:26
The running relay sends the reaction of matrix react (#93)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-17 15:34:18 +02:00

6.7 KiB

PR#93: The Running Relay Sends the Reaction of matrix react

Version: minor

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR documentation is not maintained along with new PRs; treat it as a snapshot.

Problem

matrix react takes sync.lock, which the running relay holds as long as it runs. An agent stops the relay for each batch of reactions, for about 50 s per call; meanwhile the dashboard misses the relay's report and says that the connection to the machine is lost.

Non-Goals

  • No other command through the socket, e.g. a message; only a reaction.
  • No socket in the network: a Unix domain socket of the user only.
  • No change of what matrix react does in the room.

Specification

Feature: the process that holds sync.lock sends the reaction of matrix react

Scenario#93.01: the running relay sends the reaction

… so that the relay keeps running and the dashboard keeps its report.

  • Given the relay of the machine holds sync.lock and serves control.sock
  • When an agent runs werkregie-dashboard matrix react --room "HSAdmin NG" --contains /pulls/329 ✅️
  • Then the relay sends the reaction with its own client, and the command prints the relay's outcome, e.g. REACTED
Verified by

Scenario#93.02: without a running relay the command uses a client of its own

… so that a reaction works also on a machine without a relay.

  • Given no process holds sync.lock, and a control.sock of an ended process may be left
  • When the agent runs the command
  • Then nobody answers on the socket, and the command takes sync.lock and reacts with a client of its own, as before
Verified by

Scenario#93.03: a holder of the lock that does not answer

… so that two clients never use the device's store and keys at once.

  • Given a process holds sync.lock but does not serve control.sock, e.g. a relay of an older version
  • When the agent runs the command
  • Then nothing is sent, and the command says to restart the relay and ends with exit code 3
Verified by

Scenario#93.04: a failure in the relay

… so that the agent sees why no reaction was sent.

  • Given the relay serves control.sock, and the room "HSAdmin NG" is not among its rooms
  • When the agent runs the command
  • Then the command prints the relay's message on stderr and ends with exit code 3
Verified by

Scenario#93.05: the socket takes only a reaction

… so that the socket opens no other way into the user's Matrix account.

  • Given the relay serves control.sock
  • When a process of the user sends another command, or a reaction without room, text or key
  • Then the relay sends nothing and answers with an error
Verified by

Solution

  • MatrixControl, new: the Unix domain socket <stateDir>/matrix/control.sock, one request per connection, one line of JSON each way: {"command":"react","room":…,"contains":…,"key":…}, answered by {"line":"REACTED …"} or {"error":"…"}. serve replaces a socket file that an ended process left, sets it to 0600, and answers one request after the other; ask returns null when nobody serves, so that the command falls back to a client of its own.
  • MatrixSource.sync serves the socket with its client while it syncs: the relay as well as the local mode.
  • MatrixReactor.react(dir, …) asks the socket first and takes sync.lock only when nobody answers; react(client, …) waits for the first sync and reacts with any client, the relay's or its own.
  • A holder of the lock that does not answer, e.g. a relay of an older version or one in its first seconds, makes the command fail with the hint to restart it; the command never opens a second client.
  • MatrixReactions.outcome reads the relay's line back into the outcome, so that exit code and output stay as before.
  • The socket needs no secret: the directory of the device's keys is 0700, the socket 0600, so only processes of the user reach it, which could run matrix react themselves.
  • pitestPartial mutationCrapCheck against main: the highest Mutation-CRAP is 13, MatrixReactor.entry; of MatrixControl 5, request.

Release Notes

  • matrix react sends its reaction through the running relay or local mode; neither stops for it any more.

Open Questions

None.

Prerequisite PRs

  • werkregie PR#92: each method of MatrixSource at a Mutation-CRAP of at most 15; this branch builds on it.