# Troubleshooting

> Diagnose the failures self-hosters actually hit, most common first.

The short version: the sidebar status dot tells you *whether* something is
wrong, `docker compose logs` tells you *what*. This page collects the
failures self-hosters actually hit, most common first.

## First checks

```bash
docker compose ps                          # both services up?
curl -s http://localhost:8000/api/v1/health | jq .
docker compose logs --tail=50 finance      # the API + dashboard container
docker compose logs --tail=50 imap-poller  # the email poller sidecar
```

The health endpoint is unauthenticated and returns the same state as the
sidebar dot:

| Status | Meaning |
|---|---|
| `ok` (green) | Poller checked in within 5 minutes (or IMAP isn't configured), nothing quarantined. |
| `degraded` (amber) | Last poll 5–30 minutes ago, **or** an email failed to parse in the last 7 days — the early signal that a bank changed its template. |
| `stale` (red) | No poll in over 30 minutes, or no new transaction in 14 days. |

`degraded` and `stale` describe the *email pipeline*, not the API — the
dashboard keeps working either way.

## Transactions stopped appearing

The most common failure in normal operation, and it's usually not your
setup: a bank changed its email template.

1. The dot goes amber; unparsed alerts collect under **Needs review** rather
   than being dropped.
2. `docker compose logs imap-poller` shows `Failed to parse email`.
3. Open a [parser-broken issue](https://github.com/tvhahn/tidings/issues/new?template=parser_broken.md)
   with a redacted email body — or fix it yourself with the
   [add-a-parser guide](/add-a-parser/), which is exactly the tutorial for
   this.

Meanwhile, entries recovered by AI extraction (if enabled) and manual
entries from Needs review keep the journal complete.

## The IMAP poller can't connect

Gmail sign-in failures, `[AUTHENTICATIONFAILED]`, repeated reconnects:

- Gmail needs an **App Password**, not your account password, and 2-Step
  Verification has to be on first.
- The poller backs off automatically (5s doubling to 5 minutes) and recovers
  on its own once credentials are right — no restart needed after fixing
  `.env`, but `docker compose restart imap-poller` applies it immediately.
- The [email setup guide](/self-hosting/email/#troubleshooting) has
  the per-symptom detail, including IMAP being disabled on the account.

## The dashboard won't load

- **Port 8000 already in use** — change the host side of the mapping in
  `docker-compose.yml` (for example `"8001:8000"`), or use the port snippet
  in `docker-compose.override.yml.example`. Then open
  `http://localhost:8001`.
- **Container restarting or exited** — `docker compose ps`, then
  `docker compose logs finance` and read the last screen; the failing line
  is almost always in it.
- **`exec format error` on Raspberry Pi / Apple Silicon** — images are
  multi-arch (amd64 + arm64), so this is usually a stale cache:
  `docker compose pull`, or build locally.

## Permission errors on `data/`

The named volume is owned by the container user. This appears after
switching between a bind mount and the named volume. The reset is
`docker compose down -v` — **this deletes the volume and your data**, so
take a [backup](/backup-and-restore/) first if there's anything real in it.

## Forgotten dashboard password

Stop the app, remove the `app_password_hash` key from `data/config.json`,
start it again — the dashboard returns to trust-on-first-use and prompts for
a new password. Under the Docker stack the file lives in the volume:

```bash
docker compose exec finance python -c \
  "from src.finance.app_config import update_config; update_config({'app_password_hash': None})"
docker compose restart finance
```

## Notifications aren't arriving

- With no provider configured, notifications go to the logs only — look for
  `NOTIFY [Transaction]:` lines in `docker compose logs finance`. That's the
  fallback, not a bug.
- More than one provider's variables set? Auto-detection has a precedence
  order; pin it with `NOTIFICATION_PROVIDER`. The
  [notifications guide](/notifications/#troubleshooting) walks each
  provider's failure modes.

## Demo mode won't go away

Demo mode is a config key, not an env var: set `demo_mode: false` in
`data/config.json` (or from Settings), then restart. Your real data lives in
`data/finance.db`; the seeded demo stays in `data/demo.db` and never mixes.

## Still stuck

- Health state, last poll, and version in one line:
  `curl -s http://localhost:8000/api/v1/health | jq .`
- "How do I…" questions go to
  [GitHub Discussions](https://github.com/tvhahn/tidings/discussions);
  reproducible bugs go to
  [Issues](https://github.com/tvhahn/tidings/issues) — include the health
  JSON and the last ~20 log lines, redacted.