Troubleshooting
Every message below is copied from the code. Server messages reach the app as the gRPC status message, and the app shows them as they are rather than as a raw error. Log lines go to the pod’s stdout.
Sign-in and sessions
| Message | Code | Cause and fix |
|---|---|---|
luna has no allowed accounts configured (LUNA_OWNER_EMAIL unset) | FailedPrecondition | The allowlist is empty, so every login is rejected. Set LUNA_OWNER_EMAIL. |
google sign-in not configured: google sign-in not configured (LUNA_GOOGLE_CLIENT_ID unset) | FailedPrecondition | Set LUNA_GOOGLE_CLIENT_ID. |
this account is not on this luna instance's allowlist | PermissionDenied | A valid Google account that isn’t on the list. |
google email not verified / invalid google token | Unauthenticated | A token problem on the user’s side. |
cannot reach Google to verify this sign-in — this is a server-side connectivity problem, not a problem with your account: … | Unavailable | The backend can’t reach Google’s keys. Check egress on 443. |
authorization token is not provided | Unauthenticated | The request had no Bearer token. |
invalid token: … | Unauthenticated | An unknown, expired or revoked session. The app signs out and returns to the login screen. |
session email is not on this luna instance's allowlist | PermissionDenied | The account was removed from the list. Its sessions are revoked. |
luna's session store is unavailable | Unavailable | The appfs root couldn’t be mounted, usually because CORE_ENVELOPE_KEK is missing. The server can’t check anything, so the caller’s token is not the problem. |
Chat and the model
| Message | Cause |
|---|---|
Luna's own inference server could not be reached. This is not a queue — the model plane is down or not yet configured. | Connection refused or no route. Check INFERENCE_REMOTE_URL and the plane. |
the model plane is busy right now — every slot on the shared inference server is in use. Try again in a moment. | The plane answered 429 or 503, or reported being full. |
the model did not answer in time. It is running, but this request took longer than Luna is willing to wait. | The deadline was exceeded (DeadlineExceeded). The chat deadline is 6 minutes. |
Luna could not complete that generation. The reason is in the server log; it was not a queue and not a timeout. | Anything else. Read the pod log. |
We've talked a lot this hour and the thinking plane is shared — give me about N minutes and ask again. Logging habits and your plans still work instantly. | The hourly chat cap (LUNA_CHAT_TURNS_PER_HOUR, default 30) was reached. This refusal is not journaled. |
Boot log: no INFERENCE_REMOTE_URL set — chat will fail until the inference plane is configured | Chat isn’t configured. |
Boot log: chat tier UNAVAILABLE at <url> after <d>: … — the client stays registered, so requests will still be attempted and will fail slowly rather than fast | The plane didn’t come up. … tier ready at <url> (model loaded in <d>) is the healthy line. |
In the app, Luna is unreachable — check your connection and try again.
means the request never got an answer from the backend.
Request failed with status: 0 in the browser, together with
context canceled in the server log, is a transport kill: a mobile NAT, a
carrier middlebox or the browser dropped a response that stayed silent too
long. Every RPC that waits on the model streams and re-sends a heartbeat
beat every 15 seconds for exactly this reason. If you see it, look for an RPC
that waits on the model without streaming. Don’t assume the inference plane
is down.
Vision
| Message | Cause |
|---|---|
my eyes aren't wired to a vision model yet — the platform needs its vision tier online for this | LUNA_VISION_REMOTE_URL is unset (FailedPrecondition). The text model can’t see images, so there is deliberately no fallback. |
vision is rate-limited on the shared plane — try again in ~N min | The hourly vision cap (default 12) was reached. |
N photos is more than I can look at in one go — send up to 3 at a time | More than 3 images in one request. The app batches for you. Lowering the server cap without shipping a new app build makes every batch fail. |
Voice
| Message | Cause |
|---|---|
voice transcription not configured (VOXTRAL_REMOTE_URL unset) | STT isn’t configured. The app falls back to the device recogniser. |
text-to-speech not configured | TTSD_URL is set to "". The app falls back to on-device TTS. |
voice transcription is rate-limited on the shared plane — try again in ~N min / voice is rate-limited on the shared plane — try again in ~N min | The hourly voice cap (default 120) was reached. |
Boot log: voice output DEGRADED: ttsd unreachable at <url> after <d> (<n> attempts): … | ttsd never answered its health probe during the retry window. The healthy line is voice output: ttsd ready at <url>. The probe is advisory and gates nothing. |
Boot log: voice output disabled (TTSD_URL empty) — clients use on-device TTS | Server speech was turned off on purpose. |
An unreachable ttsd is invisible to users, because the app quietly falls
back to the device voice. That is why the boot probe logs its result.
Storage and memory
| Log event or message | Cause |
|---|---|
object_store_not_configured: “OBJECTSTORE_ENDPOINT unset and no LUNA_APPFS_DIR; journals, plans and avatars cannot persist” | No storage at all. Nothing persists. |
appfs_local_root_online | No object store, but LUNA_APPFS_DIR names a real directory, which then holds everything (local development). |
object_store_recovered | A background retry succeeded. This is normal shortly after a pod restart. |
object_store_still_down: “…a Secret this pod cannot resolve (CORE_ENVELOPE_KEK, OBJECTSTORE_ACCESS_KEY/SECRET_KEY) fails here exactly like an unreachable objectd, and no retry clears it” | This is no longer transient. Read the logged error first. A missing Secret looks exactly like a network fault. |
| “the at-rest encryption key (CORE_ENVELOPE_KEK) is not configured, and the object store refuses to open a store whose contents it cannot seal” | The KEK is missing. |
no account on this request — Luna's storage is per-account and will not read or write without one | The data layer refused an unscoped request. This fails closed by design. |
A reply marked “not saved” (not_persisted) | The journal write failed or timed out (bounded at 5 s). The reply was delivered, but it won’t be remembered. |
memory: semantic recall INACTIVE: … | No embedding tier (LUNA_EMBED_URL unset or unreachable). Memory falls back to keyword search only. |
refusing to start: EMBEDDING_FORMAT: … | An unknown embedding format. The server won’t open a memory index in a vector space nobody configured. |
Startup refusals
| Message | Cause |
|---|---|
luna: N outbound endpoint(s) would carry personal data in cleartext off this cluster: followed by a list | An outbound URL points off-cluster over plaintext. Every bad endpoint is listed, so you can fix the manifest in one pass. |
admission config: … | LUNA_ADMIT_MAX or LUNA_ADMIT_QUEUE isn’t a valid number. |
Coaching preconditions
| Message | Cause |
|---|---|
design a training plan first — meal targets are derived from the training week, so the two agree. | DraftMealPlan was called with no active training plan. |
I need your body mass in kg first — every target is anchored to it, and guessing would give you numbers computed for someone else. | There is no body mass on the request or in the profile. |
level N costs C points — you have B. Keep the streaks alive. | LevelUp was called with too few points. |
a daily steps goal needs to be between 1,000 and 100,000 | SetStepsGoal was out of range. |
For step-sync messages in the app, see Habits and streaks.