Self-hosting
Troubleshooting
The messages you will actually see, what each one means, and the fix โ most of them are the game refusing something on purpose.
On this page
- Camera, microphone or share sheet does nothing
- "Photo is over 40 MB" / "Recording is over 12 MB"
- HEIC
- "That's a video โ Latch stores photos, not clips"
- "That's N characters. Your Handler wants at least 120."
- "That recording didn't say how long it is"
- "Wheel is cooling down โ 42m to go"
- "Lock up first" / "The session is frozen"
- "That's before this lock started" / "overlaps the previous lock" / "Not frozen at that time"
- Undo refused
- "This task needs a verification photo"
- The timer reads ??:??:??
- A row says "โถ logged later"
- The Handler says a reflection was missed and it wasn't
- API answers 503
- 502 from the reverse proxy
- "That file is not in the volume"
- Times are off by hours
- Logs
- Still stuck?
Most "errors" in Latch are refusals with a sentence attached. The sentence is the diagnosis. The ones people ask about:
Camera, microphone or share sheet does nothing
Cause: you are on an insecure origin (http://โฆ:8770). Browsers do not expose
navigator.mediaDevices or the Web Share API there. Fix: use your https:// address through
the reverse proxy. Do not widen the firewall or terminate TLS on the app container to get
around it โ the proxy is the door. See Install โ HTTPS.
"Photo is over 40 MB" / "Recording is over 12 MB"
The upload ceilings, checked on the stream before bytes are read. Resize the photo; keep memos under ten minutes (the recorder stops itself there).
HEIC
The browser converts these for you now. When you pick a HEIC, Latch spots it and turns it into a JPEG before uploading โ on an iPhone that just works, because iOS can decode its own format. You will see "Converted to JPEG" under the button.
Two things worth knowing:
- Conversion loses the photo's metadata, because it goes through a canvas. The original capture time and any location in the file do not survive it, so the check-in is recorded as now and the location comes from the phone instead (if you have that turned on). For a photo you are logging from weeks ago, set the time yourself with taken earlier.
- A desktop browser usually cannot decode HEIC. Chrome and Firefox on a computer will say "this browser can't convert it here" and send the file as-is, and the server will refuse it with the fix below.
If you get "That's an iPhone HEIC photo, which this server cannot decode": set the camera to Settings โ Camera โ Formats โ Most Compatible (which affects new photos only), add Convert Image โ JPEG to the Shortcut, or export the photo as JPEG from the Photos app.
Latch converts only formats it cannot otherwise read. A JPEG or PNG is uploaded untouched, which is deliberate โ a canvas round-trip would strip the metadata off photos that are working perfectly well.
"That's a video โ Latch stores photos, not clips"
Pick a video in a photo field and Latch offers you a button: ๐ท use a frame from it. One tap pulls a still out of the clip and logs that.
It is an offer rather than something that just happens, because it is not a lossless conversion the way HEIC โ JPEG is โ the motion and the sound are exactly what someone filmed a clip for, and quietly swapping in a still would be the app substituting a different thing for the one you chose.
If the browser cannot decode the clip you get "this browser can't decode that clip" โ take a still on the phone instead. Storing video itself is a separate question; see the roadmap.
Every other refusal names whatever you actually sent โ a .mov, a PDF and a TIFF each get
their own answer rather than being told to convert a HEIC they never uploaded.
"That's N characters. Your Handler wants at least 120."
The reflection minimum. Write more, or lower handler.reflection_min_chars on
Settings. Same for "That's N seconds" on a spoken one.
"That recording didn't say how long it is"
The browser sent no duration and the file's format could not be measured. Happens with some
third-party recorders posting raw audio to the API. Send duration_seconds, or record in the
app (which always sends it), or set handler.voice_min_seconds to 0 to accept unmeasured memos.
"Wheel is cooling down โ 42m to go"
The cooldown, 60 minutes by default. Set it to 0 on Settings while testing.
"Lock up first" / "The session is frozen"
The wheel spins only on a locked, unfrozen session.
"That's before this lock started" / "overlaps the previous lock" / "Not frozen at that time"
The retroactive log's guards. A clock event cannot precede the lock it lands in; a backdated lock cannot overlap the previous one; an unfreeze cannot precede its freeze. Check the when you typed. See the retroactive log.
Undo refused
The button names the reason: outside the window, or an event kind that carries side effects (training, a photo, an unfreeze, the lock itself). See Undo.
"This task needs a verification photo"
That is what requires verification means. Attach one, or edit the task in the library.
The timer reads ??:??:??
A hidden lock. You chose to see only elapsed time. It resolves when the lock ends.
A row says "โถ logged later"
You (or the scheduler, after downtime) recorded something more than two minutes after it happened. Correct and permanent โ the record stays honest about when it was told.
The Handler says a reflection was missed and it wasn't
The audit checks yesterday's local day for a non-voided reflection while you were locked at 20:00. If you wrote it but undid it, or wrote it after midnight without a when, it counts as missed. Undo the penalty within the window, and backdate the reflection with when.
API answers 503
LATCH_API_TOKEN is empty. That is the API being off on purpose. Set it and recreate the
container. 401 is a wrong or missing bearer.
502 from the reverse proxy
The container is down or restarting. docker compose ps, then docker compose logs latch.
/healthz on the raw port from the proxy host tells you whether the app is up.
"That file is not in the volume"
A media row survived a restore but its bytes did not โ the database and media/ were restored
from different moments. Copy the media folder from the same backup as the database.
Times are off by hours
LATCH_TZ (and TZ) on the container do not match where you live. Events are stored as UTC
and only displayed in LATCH_TZ, so changing it is safe and instant.
Logs
docker compose logs -f latch. Startup prints the version, the data directory, the modules
found, and whether the API is on.
Still stuck?
Report it with the version from /healthz, what you did, what you saw,
and the exact sentence on screen. Do not attach an export.
This page also ships inside the app, at /guide โ so your own instance always serves the guide for the version you are running, with the internet unplugged. Get Latch ยท Something wrong here? Tell me.