Skip to content

file_recording reports STATE, not capability — client can never enable REC #6

Description

@jwussler

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions