Summary
/api/record/status reports file_recording as state (am I recording right now), but the client contract treats it as a capability (can this host write a file at all). On this host the REC button can therefore never be enabled: it only turns true once recording has already started.
Evidence
src/api.cpp, the /api/record/status route. The 8th format placeholder is "file_recording", and it is filled with:
const bool on = rec && rec->recording();
...
JsonBool(on), // the only honest signal, and it matches
So file_recording == recording.
Why that breaks the client
The documented client rule (from the C# host's parity work) is:
Both recorders now report an explicit file_recording capability — true on Windows, false on Linux — and the client enables the button only on an explicit true, so an un-rebuilt host greys it out instead of lying.
and, in the same note:
buffering/recording are STATE; whether a file can be written is a CAPABILITY. Do not merge them again.
They are merged again. A client following the contract greys REC out permanently on this host, because the field it checks cannot be true until the thing it gates has already happened.
What the honest capability signal actually is here
The route already returns the right pair, and recorder.cpp already computes it properly:
available — true when a capture backend exists
reason / unavailable_reason() — "" when usable, else why not
recorder.cpp sets reason_ = "no record_path configured" when record_path is empty, and does a real write probe on the directory before claiming it is usable — which is exactly the capability question.
Repro
With record_path empty (the default in /etc/hamdeck-cpp/config.json as shipped):
GET /api/record/status
-> available:false, reason:"no record_path configured", file_recording:false
Set record_path, restart, and file_recording is still false until a recording is in progress — while available is now true and reason is empty. The capability changed; the field that is supposed to express it did not.
Suggested fix
Report the capability separately from the state, e.g.:
R"("recording":{},"file_recording":{},)" // state, capability
JsonBool(on), JsonBool(rec && rec->available() && rec->unavailable_reason().empty()),
Keeping recording as the state field and making file_recording mean "a file can be written" restores the contract without changing any client.
Notes
Found 09/02/2026 while wiring automatic recording of the operator's own turn on a NetLogger net. Worked around downstream for now by checking available + reason instead of file_recording, with a comment pointing here.
Summary
/api/record/statusreportsfile_recordingas state (am I recording right now), but the client contract treats it as a capability (can this host write a file at all). On this host the REC button can therefore never be enabled: it only turns true once recording has already started.Evidence
src/api.cpp, the/api/record/statusroute. The 8th format placeholder is"file_recording", and it is filled with:So
file_recording == recording.Why that breaks the client
The documented client rule (from the C# host's parity work) is:
and, in the same note:
They are merged again. A client following the contract greys REC out permanently on this host, because the field it checks cannot be true until the thing it gates has already happened.
What the honest capability signal actually is here
The route already returns the right pair, and
recorder.cppalready computes it properly:available— true when a capture backend existsreason/unavailable_reason()—""when usable, else why notrecorder.cppsetsreason_ = "no record_path configured"whenrecord_pathis empty, and does a real write probe on the directory before claiming it is usable — which is exactly the capability question.Repro
With
record_pathempty (the default in/etc/hamdeck-cpp/config.jsonas shipped):Set
record_path, restart, andfile_recordingis still false until a recording is in progress — whileavailableis now true andreasonis empty. The capability changed; the field that is supposed to express it did not.Suggested fix
Report the capability separately from the state, e.g.:
Keeping
recordingas the state field and makingfile_recordingmean "a file can be written" restores the contract without changing any client.Notes
Found 09/02/2026 while wiring automatic recording of the operator's own turn on a NetLogger net. Worked around downstream for now by checking
available+reasoninstead offile_recording, with a comment pointing here.