Draft. Written against the spec, ahead of the code. The words are the intent; the software is not there yet.
Troubleshooting
The things that go wrong, what they look like, and what they mean. Most of them are macOS telling you something quietly.
Nothing prompts, and nothing records
You arm a camera, macOS does not ask, the row goes ochre, and the file is empty. Open System Settings → Privacy & Security → Camera: Rheocles is not in the list.
That is not “denied”. An app that has never been able to ask does not appear
at all, and there are two ways to be unable to ask under the hardened
runtime: a missing Info.plist usage description, or a missing entitlement.
Both are required. A release build from this repo has both; a local build
signed differently may not.
If you are building from source, codesign --verify will report the bundle
valid even when --deep has skipped an executable in Contents/Resources.
Only notarisation catches it. Sign inside out.
Displays are missing after granting Screen Recording
The grant takes effect on the daemon’s next launch, not the current one.
Quit Rheocles and open it again; GET /streams then lists every display and
permissions.screen reads authorized. The first stream list in a fresh
process is what raised the prompt, and that process keeps the answer it had
when it started.
Arming answers 403
permission_denied: macOS has not granted the device class this stream
needs. For camera and microphone the prompt is raised first, so a 403 means
it was refused — the grant is under System Settings → Privacy & Security.
For system audio the grant is System Audio Recording, under Screen & System
Audio Recording.
Arming answers 503
device_unavailable: the device is gone, busy, or refused the configuration.
A camera another app holds exclusively, a display that was just unplugged, a
window that closed between the list and the arm. Refresh the list and try
again; if it persists, the device is the problem, not the request.
The armed camera is 720p
Arming takes the device’s current format and holds the configuration
lock for the whole armed period. A camera another app left at 720p stays at
720p until that app or the user changes it. active on the stream says what
you will get; set the format in the other app, or disarm, change it, and arm
again.
framesSeen is not counting
An armed stream that delivers frames counts up. One that does not is stuck — a window that went off-screen, a device that stalled — and a take started now would record nothing from it. Disarm and arm again; if it stays at zero, the device is not delivering.
The port is in use
The daemon comes up on 7447 and 7448. If another daemon is already there — the NativePHP app’s, or one left over — the app uses it rather than starting another, which is the intended behaviour. If something else is on 7447, the app says so and will not start a daemon until the port is free. Sonocles is on 7357 and 7358 and does not collide.
409 on create
POST /takes was refused because the destination already exists, or a take
is already recording. Rheocles never silently suffixes a path — a client
that named a path meant it. Pass overwrite: true if you meant to replace
it, or name it something else. One active take at a time in the MVP.
Create refused for disk space
The pre-flight estimated the take’s size from the armed set’s measured
bitrates and expectedDuration (default thirty minutes) and found the free
space short. The error carries both numbers. Free space, shorten the
estimate if you know the take is short, or disarm the stream that is costing
the most.
The manifest says incomplete
A write failed mid-take — the disk filled, a volume unmounted, a device
disappeared — and Rheocles stopped cleanly rather than continuing with a
lie. reason says what. Every file is playable up to the last fragment,
and the manifest is truthful about what it has.
The manifest says recording and nothing is running
The daemon died mid-take. The files are fragmented MOVs and play to their
last fragment; the manifest has every stream’s started, timecode and
last known frame count. Nothing needs reconstructing — but nothing will mark
the take complete either, and the state is the honest one.
Requests answer 401
The token does not match. It was rotated in the popover — rotation
invalidates the old token for everyone — or the client is reading a stale
copy. Re-read ~/Library/Application Support/Rheocles/token.
My late-joined file lands before the take
The stream joined after midnight into a take that started before it. Its
time-of-day timecode is near zero, and the editor placed it twenty-four
hours early. Every camera and field recorder shares this; there is no fix
inside the file. Place it from the manifest’s started, which is absolute
UTC — see Timecode and sync.
Two files are a frame apart
Look at driftMs for each stream in the manifest. A capture device’s clock
is not the host’s, and a long take can drift by a frame or two; the number
is measured per stream and is there to conform with. null means it was
not measured — a stream that wrote no frames — and is not the same as 0.
Six cameras are still armed
The mark in the menu bar is drawn in outline and the popover rows are ochre. That is what armed looks like, and armed streams cost CPU and hold their devices until you disarm them. Nothing disarms on your behalf.