Repository navigation
Troubleshooting
Every handled error Tippani logs carries a stable code of the form
TIP-<SUBSYS>-<NNN> (for example TIP-SRCH-002). When something goes wrong,
find the code in docker logs and look it up here.
-
[error]lines and the one-line-per-request access log go to stderr;[warn],[trace]and ordinary progress lines go to stdout.docker logs <container>merges the two, so it shows everything. - Lines are tagged:
[error](a failure),[warn](recovered/degraded, worth knowing),[trace](deep per-operation detail, only whenTIPPANI_LOG_LEVEL=debug). - Set
TIPPANI_LOG_LEVEL=debugto turn on full per-operation tracing when you are hunting an intermittent problem; leave it unset for normal, quiet operation. - An admin can read the same lines without a shell, in Settings → Jobs, on the System logs card: kept for 30 days, filtered by level, time and keyword (a code typed into the search bar finds its lines), and exported as Markdown to hand to somebody. Each job's own log, every outward call it made included, is on the job in Past jobs. Neither keeps a key or a password: a request's line keeps only its query's names, and an outward call's hides every credential in its address. The terminal's lines are unchanged.
The codes are defined in internal/olog/codes.go; a build-time test keeps this
document and that registry in lockstep.
There is no code for this one, because it happens before the logging subsystem
and the HTTP server exist. The container exits 1 and the restart policy loops
it, and docker logs holds two lines and nothing else.
| Line | Cause | What to do |
|---|---|---|
open db: … unable to open database file followed by … is owned by uid 0, but tippani runs as uid 65532 …
|
The image runs as the non-root user 65532, and the data directory is not writable by it. Almost always a Docker bind mount whose host directory did not exist when the container first started: Docker creates a missing bind source as root. |
Run the chown -R 65532:65532 <path> the second line prints, against the path on the host. A named volume is chowned by the image and never needs this. |
create data dir: … permission denied |
Same cause, one level up: the parent of the data directory is not writable either. | Same fix, applied to the parent. |
migrate: … |
The database opened but a schema migration failed. Not a permissions problem. | Read the migration error. Restore the backup taken before the upgrade; migrations are forward-only, so an older image cannot open a newer database. |
Two things that look like this and are not: a healthcheck failure does not
restart the container (only an unhealthy status), and a bad TIPPANI_LOG_LEVEL
is ignored rather than fatal — including the quotes in - TIPPANI_LOG_LEVEL="debug",
which Compose keeps in list form, leaving tracing silently off.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-HTTP-000 |
Unclassified internal server error (the generic 500 fallback). | A database, transaction, or encoding failure that has not yet been given a specific code. | Read the full [error] line — it includes the request method, path, and underlying cause. If it recurs, the handler should be given a specific code. |
TIP-HTTP-001 |
The TLS certificate/key pair changed on disk but failed to re-load; the previous pair is still served. | A renewal wrote a malformed file, or wrote the cert and key non-atomically (Tippani retries once the second file lands). | Check TIPPANI_TLS_CERT/TIPPANI_TLS_KEY point at a matching PEM pair. HTTPS keeps working on the old certificate until it expires, so fix the files and the next handshake picks them up — no restart needed. |
TIP-HTTP-002 |
An API request waited ten seconds for a database connection, none came free, and it was answered 503 without changing anything. | Every connection in the pool is held and not coming back: the state issue #40 describes. The line ends with the requests in flight, oldest first. | If it is a single line during a restore, a reset or a large import, it was that. If it repeats, save the log and the goroutine dump described under HEALTH below, then restart Tippani. |
TIP-HTTP-003 |
A request was still running after the server's 60-second write deadline. Each is named once, with its request id. | Expected for a backup download, a restore, an in-app update and a very large import approval, which run as long as they need. On any other route it is a request stuck waiting. | Find the same req rN on its access line once it finishes, or on a TIP-HEALTH-001 line if the pool is held. A stuck ordinary request is worth a report with the log. |
TIP-HTTP-004 |
A request's handler panicked. The server recovered, closed that one connection and went on; the line carries the stack. | A bug in Tippani. The request is still in the log as a 500, with its access line. | Nothing to fix on your side. Please report the full [error] line, with the access line just before it. |
/healthz, which the image's HEALTHCHECK calls every 30 seconds, answers 200 when a
request arriving now could reach the database and get an answer, and 503 with the
reason when it could not. A failed check writes one of these lines, ending with the
requests in flight; the first check that passes afterwards writes
[health] healthy again after N failed check(s). Docker does not restart a
container for being unhealthy; only an orchestrator or an autoheal container does.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-HEALTH-001 |
The health check could not get a database connection within two seconds. Three failed checks 30 seconds apart mark the container unhealthy. | Every pooled connection is held. The line names the requests in flight; the oldest is usually the one holding things up. A single line, or a few, while a large import approval, a restore or a backup runs is a busy pool: a writer can hold a connection for five seconds, longer than this check's two. | If it is one line followed by [health] healthy again, it was a burst and needs nothing. If it repeats and docker ps says unhealthy: save docker logs tippani > tippani.log 2>&1 and docker inspect --format '{{json .State.Health}}' tippani > health.json. Then docker kill --signal=QUIT tippani: Tippani does not catch SIGQUIT, so Go writes every goroutine's stack to the log and exits. Save that with docker logs tippani > goroutines.log 2>&1 before anything recreates the container, then docker start tippani, because a container stopped by docker kill counts as stopped by hand and its restart policy does not bring it back. Attach all three to a report. |
TIP-HEALTH-002 |
The health check got a connection, but the database did not answer its read. | The database is closed: for a moment during a restore or a reset (a single line followed by "healthy again" is that), or for good if a reopen failed. | A single one during a restore or reset needs nothing. If it persists, read the log above it for the restore or reset that failed, and restart. |
| What you see | What it means |
|---|---|
The health check is green, and there is no TIP-HTTP-003 line and no access line for the page you loaded. |
Your requests are not reaching Tippani. Loading a page always writes an access line, even when the database is stuck, because the page itself needs no database. Compare docker exec tippani /tippani healthcheck (inside the container) with a request from the host; if only the host fails, the problem is the port publish or the network in front of it. |
| The screen stays empty for about twenty seconds, then shows the sign-in form although you are signed in; signing in answers "changed nothing". | The API is refusing requests because no database connection is free. Look for TIP-HTTP-002, then TIP-HTTP-003. |
| The page's frame loads and the screen stays empty for good. | An API call got past the door and is waiting inside a handler. It is named by TIP-HTTP-003 once it passes a minute. |
The health check reports Client.Timeout exceeded with no code. |
The server did not answer within three seconds at all, which points at the storage or the host rather than at the pool. |
The image has no shell, wget or kill, so these are all run from the host.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-UPDATE-001 |
A Docker Engine API call failed during self-update (identify self, pull the image, or launch the recreater). | The socket/proxy lacks a needed permission (a socket proxy must allow CONTAINERS=1, IMAGES=1, POST=1), the registry was unreachable, or the container name could not be resolved. |
Read the [error] line for the Engine's status and message. For a socket proxy, verify the permission env vars on the proxy container; for the raw socket, verify the mount and the group_add gid. A message reading inspect self: docker 404 for <a> and <b> names the two references it looked for — its own id from /proc and its hostname — and means the daemon knows neither, which normally means this process is not in a container that daemon can see. The guided manual command in Settings always works meanwhile. |
Not an error code, because nothing here fails: the log shows APPLY requested…,
then recreater launched…, the image pulls, and the container goes on running the
build it started on. Settings reports the restart happened without the version
moving.
The recreater is a one-shot Watchtower, launched detached and AutoRemove, so
Tippani never sees its output — if it dies, the app has nothing to report.
Before 3.x the default helper was containrrr/watchtower, whose last release
(1.7.1, 2023) negotiates Docker Engine API 1.25. A current daemon refuses it —
client version 1.25 is too old. Minimum supported API version is 1.40
— and the helper then panics and exits without touching anything.
Newer builds default to nickfedor/watchtower, the maintained continuation, which
negotiates the daemon's own API version. If you are on an older Tippani, or you
pinned the helper yourself, set it explicitly:
environment:
TIPPANI_UPDATER_IMAGE: nickfedor/watchtowerTo confirm what the helper is doing, run it in the foreground once — the same command Tippani runs, minus the detach:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
nickfedor/watchtower --run-once --cleanup <your container name>| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-STORE-001 |
SQLite PRAGMA quick_check could not run. |
The database file is unreadable or the handle is broken. | Check the data volume is mounted and readable. Restart to re-run startup repair; if it persists, restore from backup. |
TIP-STORE-002 |
quick_check found page-level corruption. |
An unclean shutdown or an unreliable storage volume tore the file. | Startup attempts automatic recovery. Ensure the container stops gracefully (see below) and the volume honours fsync. If recovery fails, use Profile → Rebuild search index, then restore from backup as a last resort. |
TIP-STORE-003 |
A full-text index could not be reconstructed in place. | The index shadow tables are too corrupt to drop and recreate. | Recovery escalates to a whole-database rebuild automatically. If that also fails (TIP-STORE-004), restore from backup. |
TIP-STORE-004 |
Whole-database recovery (rebuild from intact content) failed. | Corruption has spread beyond the search indexes into base tables. | Restore from backup. Search stays unavailable until Profile → Reset all data or a restore. |
TIP-STORE-005 |
WAL checkpoint on shutdown failed, or could not finish. | A reader still held the database as the server stopped, or another connection was still writing two seconds into the checkpoint (a stuck request, a job shutdown gave up on, a sqlite3 shell); the line then says how many of the WAL's frames were folded back anyway. |
Harmless — the WAL is valid and replays on the next start. If frequent, check for a stuck long-running request at shutdown. |
TIP-STORE-006 |
Factory reset could not delete a database file. | The OS still held the file handle (common briefly on Windows). | The server reopens the existing database and reports the error; retry the reset. |
TIP-STORE-007 |
A one-time upgrade pass failed and was skipped. | A pass is the third kind of database change — once per database, on an instance that already existed, because a release changed what something means (internal/store/onetime.go). It runs from Migrate(), where a returned error would stop the app from starting, so a failure is logged instead. The commonest cause is the pass's own query meeting data it did not expect. |
The app boots normally and the pass is left unrecorded, so the next start tries it again — which means this line repeats on every boot until it succeeds or the pass is removed. What is lost is whatever that pass does; for the 2.2.0 pass that is one advisory notice in Settings → Metadata sources, and nothing else. |
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-SRCH-001 |
A full-text search query failed at runtime. | The FTS index is corrupt or has drifted from its content table. | Tippani reconstructs the index and retries automatically within the same request. No action needed unless it is followed by TIP-SRCH-002. |
TIP-SRCH-002 |
A corrupt FTS index could not be reconstructed while serving a search. | Page-level corruption too severe for an in-place rebuild. | Restart the server (startup repair escalates to a full recovery) or run Profile → Rebuild search index. Library data is never affected. |
TIP-SRCH-003 |
A search result row failed to scan and was dropped. | A SELECT and its target struct drifted apart (usually a migration added a column). |
Report it — the search query and its scan target need realigning. |
TIP-SRCH-004 |
A fuzzy-search vocabulary read failed; typo correction was skipped. | A corrupt FTS index that also failed its one-shot repair, or the 0016 fts5vocab migration did not apply. |
Run Profile → Rebuild search index or restart. Exact search still works; only the zero-hit typo-correction pass was skipped. If it persists, check for TIP-SRCH-002. |
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-LOCALE-001 |
Two files in data/Locales resolve to the same language code; only one is loaded. |
The language code is the file name with the extension removed, lower-cased — so FR.txt and fr.txt are both fr, as are fr .txt and fr.TXT. Only possible on a case-sensitive filesystem (Linux); Windows and macOS refuse the second file themselves. |
The log line names every file that collided and which one won — the exact lower-case spelling is always preferred, so fr.txt beats FR.txt. Rename or delete the others. Nothing is lost: the losing files are read past, not touched. |
These all mean one row of a list/collection failed to scan and was skipped, so a
list came back mysteriously short (the class of bug the 0.6.4 favourites fix
exposed). The cause is almost always a SELECT that drifted from the Go struct it
scans into — usually a migration that added or reordered a column without updating
the query. The fix is to realign the query and the scan target; the log line names
the subsystem and the underlying error.
| Code | Subsystem |
|---|---|
TIP-ANNO-001 |
Annotations list |
TIP-DLG-001 |
Dialogues list |
TIP-UTT-001 |
Quotes list (the ones with no book or film) |
TIP-BOOK-001 |
Books list / count / tags |
TIP-MOVIE-001 |
Movies list / count |
TIP-PEOPLE-001 |
People list / names / orphan images |
TIP-REVIEW-001 |
Quiz / practice candidate rows |
TIP-REVIEW-002 |
An answer's row in the recall history (item_recalls). The grade itself was saved — the log sits beside the schedule rather than inside it, so the card is scheduled correctly and the only loss is one row the review popup can draw. Nothing to do unless it repeats. |
TIP-REVIEW-003 |
The photographs for the credits on Settings → Review → Never asked about. The list itself is unaffected: every chip draws its stand-in instead of a face, which is also what a credit nobody has fetched a person for looks like. Nothing to do unless it repeats. |
TIP-EXPORT-001 |
Export rows |
TIP-BULK-001 |
Bulk-selection id rows |
TIP-TAG-001 |
Genres / tags list |
TIP-BOARD-001 |
Quote boards list |
TIP-ANTH-001 |
Anthologies list / an anthology's entries |
TIP-STATS-001 |
Stats aggregate rows |
TIP-STICKER-001 |
Stickers list |
TIP-TRASH-005 |
The bin list |
TIP-ADMIN-001 |
Admin user list |
TIP-META-001 |
Metadata console / library rows |
TIP-IMPORT-002 |
Import staging queue rows (batches / works / quotes) |
A bulk import parses into a holding area and nothing enters the library until it is approved, so these failures leave the library untouched by construction: a failed stage means the file was not taken in, and a failed approve means the work it failed on, and every work after it, is still queued. Nothing is half-applied: a staging is one transaction per file, and an approval one per work — since 3.1.0, when an approval became a queued job that a Stop can land in, so the works it approved before a failure stay in the library and its log in Settings › Jobs names them.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-IMPORT-001 |
A parsed import could not be written into the staging tables, or an upload could not be kept (or read back) for its import's job; nothing was staged. | A database write failed mid-batch, or the data directory's .jobs-spool could not be written or read (disk full, permissions, or corruption — check for TIP-STORE-002). |
Retry the upload. Nothing is wrong with the file; the library and the existing queue are unchanged. |
TIP-IMPORT-003 |
Approving staged quotes failed; the work it failed on rolled back. | A write failed while resolving the destination work or inserting quotes — often a book or film that was deleted while its quotes sat staged. | That work's quotes, and every work's after it, are still in the queue (the works before it were approved, and the approval's log in Settings › Jobs names them), so retry the approval. If it recurs, approve one batch (or one work) at a time to find the row that fails. |
TIP-IMPORT-004 |
A staging-queue mutation (bulk edit, retarget or discard) failed; the queue is unchanged. | A write failed, or a retarget named a book/film that was deleted mid-edit. | Reload the queue so it reflects the current library, then retry. Nothing was partially applied. |
TIP-IMPORT-005 |
No signature matched and no parser claimed the upload; nothing was staged. | The file is text, but it is not one of the eight formats the drop target reads — a hand-written note, a CSV, or a saved page the site has since restructured. | The row offers Read this as…: pick the format it came from and it is parsed again. If the right format is already picked and it still fails, the page was probably saved as “complete” rather than “HTML only”. |
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-META-002 |
A metadata provider key or setting could not be read. | A settings read failed, so a lookup degrades to "unconfigured". |
Check the database is healthy; re-save the key in Settings → metadata keys. |
TIP-META-010 |
Genres failed to persist even though the request returned OK. | The genre write transaction failed to begin or commit. | Retry the edit. If it recurs, check for database corruption (TIP-STORE-002). |
TIP-COVER-001 |
An on-demand image download failed: a cover or poster refetch, a role's picture, or a person's headshot. | The provider was unreachable or returned no image. The line names the address it asked for. | Retry later; a picture uploaded by hand still works. A headshot's identity and facts are kept without it. |
TIP-BOOK-002 |
A book cover could not be fetched on create; the book was saved without one. | The cover URL was unreachable or blocked by the SSRF guard. | Add a cover later via the cover picker; the book itself saved fine. |
TIP-MOVIE-002 |
A movie poster could not be fetched on create/update; saved without one. | The poster URL was unreachable or blocked. | Add a poster later; the movie saved fine. |
TIP-PEOPLE-010 |
Garbage-collecting orphaned people rows/images failed. | A delete query failed after the parent row was removed. | Harmless to users; orphaned rows/images may accumulate. Investigate if disk grows. |
TIP-STICKER-002 |
A starter sticker could not be copied into a user's library; the rest of the set still went in. | The MediaCover directory is not writable, or the disk is full. |
Fix the volume permissions / free disk space. Uploading your own stickers uses the same directory, so this affects that too. The starter set is offered once per instance and is not retried — upload the shapes you want, or restore from a backup taken before the upgrade. |
TIP-BOOK-003 |
A user-supplied cover URL failed to fetch while editing a book; the save was rejected. | The URL doesn't point directly at an image, the host blocked/hotlink-protected the request, it exceeded 10 MB, or the fetch timed out/was refused (private-IP guard). | Read the full [error] line for the underlying cause; try a direct image link or upload the file instead. |
TIP-MOVIE-003 |
A user-supplied poster URL failed to fetch while editing a movie; the save was rejected. | Same causes as TIP-BOOK-003. |
Read the full [error] line for the underlying cause; try a direct image link or upload the file instead. |
TIP-PEOPLE-002 |
A user-supplied person image URL failed to fetch; the save was rejected. | Same causes as TIP-BOOK-003. |
Read the full [error] line for the underlying cause; try a direct image link or upload the file instead. |
TIP-META-011 |
A provider lookup failed while previewing a re-verify; the item reported fetch_failed. |
The source (Google Books / Open Library / Amazon / TMDB / TheTVDB) was unreachable, rejected the key, or is out of quota. | Retry later; check the key/quota in Metadata → Sources. The rest of the batch still previewed. |
TIP-META-012 |
Writing an approved re-verify change failed for one item. | A database write failed mid-apply (or an id collided with another item's). | Read the full [error] line; retry the apply for that item. Other items in the batch were unaffected. |
TIP-META-013 |
An approved cover/poster/portrait failed to download on re-verify apply. | The image URL was unreachable or blocked by the host allowlist. | The item's text fields were still applied; re-run the re-verify or set the image manually. |
TIP-META-015 |
A fetched field could not be encoded during a fill the gaps run over a selection, so that one field was skipped. | The provider returned a value of a shape the writer does not accept — effectively a provider or parser change, not a configuration problem. | The item's other missing fields were still filled. Re-run fill the gaps, or set the one field by hand; the log line names it. |
TIP-META-014 |
An on-demand book or movie lookup failed at the provider; the client saw a generic 502 ("lookup failed"). | Google Books / Open Library / TMDB / TheTVDB was unreachable, rejected the key, or is out of quota — the real cause is on the same [error] line. |
Read the full [error] line: a 401 / "rejected the key" means fix the key in Metadata → Sources; a 429/quota means retry later or add your own key; a connection error means the container can't reach the provider (check egress/DNS). |
TIP-PEOPLE-003 |
An on-demand person link/portrait lookup failed at the provider; the client saw a generic 502 ("lookup failed"). | For authors, Open Library was unreachable or errored; for actors/directors, TMDB was unreachable or rejected the key (including the built-in fallback key hitting its rate cap). The real cause is on the same [error] line. |
Read the full [error] line: a TMDB 401 means fix/replace the key in Metadata → Sources; a 429 means retry later; a connection error means the container can't reach the provider. |
TIP-META-016 |
A speaker remap rewrote a film line's character but deliberately left its actor alone, because the line names a different number of characters than actors. | An ensemble line — "Sam, Frodo" spoken against one actor, or three actors against two characters — has no slot-for-slot pairing to follow. | Nothing is broken and nothing was guessed. The character is remapped; the actor field is left exactly as it was, because inventing a pairing would put the wrong actor against the wrong character invisibly and it would read as your own typing. Set the actor by hand on that line if it needs one. |
TIP-META-017 |
An IGDB game lookup failed; the client saw a generic 502, or a 503 when no key is set at all. | IGDB needs BOTH a Twitch client id and a client secret, and Twitch answers a wrong one with 400 invalid client rather than a 401 — so "rejected the credentials" usually means one of the two is mistyped or the app was deleted. Otherwise: out of quota (4 req/s), or the container cannot reach api.igdb.com / id.twitch.tv. |
Check both values in Metadata → Sources; they are a pair and a valid id with a stale secret fails the same way as a wrong id. Register an application at dev.twitch.tv to get a fresh pair. A connection error means egress or DNS, not the key. |
TIP-META-018 |
No Wikidata item claims a game's IGDB slug, so its voice cast could not be fetched. The game itself was saved. | Expected for most games and not a fault. Wikidata is the only free structured source of game voice credits and its coverage is thin — of 24 well-known titles measured, 10 had a usable cast and The Witcher 3, Mass Effect 3, Persona 5, Disco Elysium and BioShock had none at all. | Nothing to fix. The cast is left blank and is hand-editable: type the credits into the game's Details, and they are kept exactly as a fetched cast would be. |
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-SHELF-001 |
A shelf cap was asked for a media_type the shelf does not recognise; the tightest (film) cap of two was used. |
A media type reached the shelf that shelfCap was never taught — either a new one added without an arm, or a corrupted movies.media_type value. |
Check the log line for the offending value. If it is a media type the app should support, shelfCap in internal/httpapi/shelf.go needs a decided arm rather than a fallthrough; if it is junk, correct the row. Nothing is lost either way — the cap is only a client-side nudge. |
Every book, film, show and game owns a list of characters with the actor beside
each (the voice actor on a game; a book's list has no second column at all).
It is seeded by whichever metadata provider pinned the title and is editable by
hand, and the rule that keeps those two apart is that a refetch never
overwrites a row you have touched — see the header of migration 0048.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-CAST-001 |
A cast row failed to scan, so that character was left out of the list. | A SELECT that drifted from the Go struct it scans into — usually a migration that added or reordered a column in work_cast without updating the query. |
Cosmetic per row and reported as a 500 only if the whole read fails. Realign the query and the scan target; the log line names the work and the underlying error. |
TIP-CAST-002 |
A cast row's folded lookup keys could not be rewritten by the boot-time repair; the row kept the keys it had. | Almost always the pair UNIQUE: two rows on one work whose names differ only by case or by punctuation (Éowyn and éowyn) become the same row once both are folded, and SQLite refuses the second. This repair runs on every start, so refusing to boot over it would be far worse than skipping it. |
Nothing breaks. That one row goes on being missed by the quote form's actor auto-fill, exactly as it was before the repair ran. Open the work's cast, delete whichever of the two duplicates is wrong, and the next start folds the survivor. |
TIP-CLEANUP-001 |
A quote could not be read during the Settings cleanup sweep, so it was skipped. | A SELECT/struct drift, or a row whose text column holds something the scan could not decode. The sweep reads every quote in one pass, so refusing the whole list over one unreadable row would hide every other finding. | Nothing is changed by this sweep at all — it only lists. That one quote is absent from the list; every other quote is still reported, and the count of quotes scanned in the reply tells you one went missing. |
TIP-CLEANUP-002 |
An accepted correction on the Stray marks page could not be written. | The UPDATE failed for a reason other than a duplicate — a duplicate is counted and reported to the page rather than logged. Disk, a lock held past the busy timeout, or schema drift on the field being rewritten. |
The whole batch rolls back, so every quote still holds exactly the words it had. This is the only path in the feature that writes to a quote, and it writes inside one transaction for precisely this reason. |
TIP-CLEANUP-003 |
A finding you ignored was not recorded, or the ignored set could not be read. | A read or write against cleanup_ignores (0052) failed — usually disk or a lock. |
Ignoring again works once the underlying problem is gone; the quote is untouched either way. If it was the READ that failed the page shows nothing at all rather than an unfiltered list, deliberately: an unfiltered list re-offers every finding you have already dismissed. |
Deleting a book, film, quote or account writes a JSON snapshot of the whole
subtree to the trash table first, then deletes the rows. A failure to write the
snapshot REFUSES the delete: nothing is removed, which is the safe direction for
a feature whose whole purpose is not losing things.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-TRASH-001 |
A delete could not be binned, so the delete was refused and nothing was removed. | A database write failed (disk full, or corruption — check for TIP-STORE-002). |
Free disk space and retry. The item is still there; nothing is half-deleted. |
TIP-TRASH-002 |
A binned cover/poster could not be parked, restored or purged. | The MediaCover volume is not writable, or the file was already removed by hand. |
Cosmetic: the row is unaffected. Parking fails towards KEEPING the file, so a stale image may sit in MediaCover/ or MediaCover/trash/; both are collected by a later sweep. |
TIP-TRASH-003 |
A restore failed and rolled back; the bin entry is still there. | An id collision (should be impossible — see id_floor), a username already taken when restoring an account, or a write failure. |
Read the full [error] line. The entry is intact, so retrying is safe. |
TIP-TRASH-004 |
The bin's retention sweep failed; expired entries and their files are still on disk. | A database or filesystem error during the sweep. | Harmless to data; the sweep runs again on the next start and once a day after. Disk use stays higher than the retention setting implies until it succeeds. |
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-BOOK-004 |
A book's own chapter list could not be read, so the chapter number and name fields offered no suggestions. | A SELECT over annotations' two chapter columns failed — schema drift, or the database unreadable at that moment. |
Cosmetic. The fields still accept anything you type, and the highlight saves exactly as before; only the dropdown is empty. |
TIP-CAST-003 |
A requested IMDb cast fetch failed. | Network (IMDb unreachable, timed out at the shared 10s client timeout), a page that carries no embedded document, or the write. A 404 from IMDb is reported to the reader as "no title with that id" rather than logged. | The work's existing cast is unchanged — the merge runs in one transaction, so nothing partial is stored. Check the id against the page in a browser, then press it again; nothing retries on its own, by design. |
TIP-CAST-004 |
A requested TheTVDB cast re-pull failed. | Network (TheTVDB unreachable, timed out at the shared 10s client timeout), a key that has expired or been revoked, or the write. A title with no TheTVDB id is refused as a 409 before anything is fetched, and a missing key as a 503; neither is logged here. | The work's existing cast is unchanged — the merge runs in one transaction, so nothing partial is stored. Check the key in Settings → Metadata, then press it again; nothing retries on its own, by design. |
TIP-CAST-005 |
A work page's picture pass could not read the work's cast, or could not store a role's picture or an actor's headshot it had fetched. | The database refusing a write (a full disk, a file made read-only) or failing a read. A download that failed is not this code: it is logged where it failed, as TIP-COVER-001, a role's picture and a headshot alike. |
That picture is missing and the page draws what it draws without one; the rest of the pass went on. Fix what the database's own error names, then open the page again: it asks for whatever is still missing. |
TIP-BACKUP-001 |
The backup's database snapshot (VACUUM INTO) failed; no archive was produced. |
Disk full, or the live database is corrupt (TIP-STORE-002). |
Free disk space; run Profile → Rebuild search index or check integrity, then retry. |
TIP-BACKUP-002 |
The backup archive could not be written or promoted into backups/, or a safety copy could not be sealed, or its download was cut short. |
Disk full or a permissions problem on the data volume; for a download cut short, the browser's connection dropped. | Free disk space / fix volume permissions and retry. A safety copy cut short is taken again from the restore or reset prompt: its download works once. |
TIP-BACKUP-003 |
Restore could not extract the backup archive to staging. | Disk full (restore needs roughly the archive's expanded size free) or a truncated archive. | Free disk space; re-create the backup if the archive is damaged. |
TIP-BACKUP-004 |
The restore swap failed; the previous data was rolled back intact. | A file in the data dir was locked, or the restored database failed to open/migrate. | Read the full [error] line; nothing was lost — retry after fixing the cause. |
TIP-BACKUP-005 |
The restore rollback failed; the server exited so Docker restarts it cleanly. | Cascading I/O failure during rollback. | The container comes back on whatever is on disk; the previous data dir is preserved in .pre-restore-<ts> inside the data volume for manual recovery. |
TIP-BACKUP-006 |
Backup/restore temporary files could not be cleaned up, or a safety copy's file could not be removed after its download or once it had waited too long. | A lingering file lock (Windows) or permissions. | Harmless to data; delete stray .backup-* / .restore-* dirs, .safety-*.tpbk files and old backups/*.partial files to reclaim disk. A restart removes every .safety-* file by itself. |
TIP-BACKUP-007 |
An uploaded restore archive could not be spooled to disk. | Disk full (the upload needs the archive's size free on the data volume) or a permissions problem. | Free disk space / fix volume permissions and retry the upload; live data is untouched. |
TIP-BACKUP-008 |
The backup could not leave the job history and system log out of its database snapshot; no archive was produced. | Disk full (stripping rewrites the snapshot once more), or the snapshot could not be written in the backup's staging directory. | Free disk space on the data volume and retry; live data is untouched. |
TIP-BACKUP-009 |
A restore could not carry the job history and system log over from the database it replaced. The restore went ahead; the restored server starts with no history. | The replaced database in .pre-restore-<ts> could not be read (removed by hand, or damaged). |
Nothing to fix for the library, which restored. The old history is still in .pre-restore-<ts>/tippani.db until the next restore. |
TIP-BACKUP-010 |
An uploaded restore archive stopped arriving: nothing came for a minute, so the upload was given up. Nothing was changed. While an admin's upload waited, no job could start; they can again. | The browser's connection dropped mid-upload (a laptop asleep, a phone that lost its signal), or a reverse proxy in front of Tippani stopped passing the body on. | Upload again on a steadier connection. Behind a proxy, check that it streams request bodies and does not time them out. |
TIP-AUTH-001 |
A single sign-on could not start, or the provider's answer failed validation. |
TIPPANI_OIDC_ISSUER unreachable from the server, an issuer that does not match its own discovery document (a trailing path or http/https mismatch), a redirect URL the provider does not have registered, or a clock skewed by more than a minute. The full [warn] line names which check failed. |
Fix the provider or the TIPPANI_OIDC_* settings and press the sign-in button again; password sign-in is unaffected. |
TIP-NOTIFY-001 |
A Pushover message was not accepted. | Network, TIPPANI_OFFLINE switched on, or an invalid user key / application token (Pushover answers 4xx). |
Check the keys in Profile → Notifications and press Send a test; the import, fetch or backup that triggered the message completed regardless. |
The database keeps a copy of the system log and every job's log for 30 days, read in
Settings → Jobs: Current jobs and Past jobs hold each job with its log and an Export to
Markdown (a reader sees their own jobs, an admin everybody's), and the System logs card, an
admin's alone, holds the system log, filtered by level, time and keyword. The same reads answer
a script, or a browser signed in to Tippani: /api/jobs?view=past lists the jobs that have
ended, newest first (view=current: the ones waiting or running), /api/jobs/<id> is one job
with its log and /api/jobs/<id>/log.md that log as Markdown; /api/admin/logs (narrowed with
level, q and a window: since, milliseconds back from the server's own now, or from and
to in unix milliseconds, and upto, the newest line id to include; each answer names the
window it read as from and upto) and /api/admin/logs.md (the same filters, or ?all=1 for
everything kept) are the system log. stdout and stderr are unaffected: docker logs still has
every line, including the ones a code below says the database did not keep.
| Code | Meaning | Likely cause | What to do |
|---|---|---|---|
TIP-LOG-001 |
The log writer stopped on an internal error and restarted; the lines it was writing were not kept in the database. | A bug in Tippani. The [error] line carries the stack. |
Nothing to fix on your side, and nothing else is affected. Please report the full [error] line. |
TIP-LOG-002 |
A batch of log lines could not be written to the database; those lines were not kept there. | Another writer held the database's write lock through every retry (a long import or restore), the disk is full, or the database is damaged (TIP-STORE-002). |
Free disk space if it is short. A one-off during a large import is harmless: the lines are still in docker logs. |
TIP-LOG-003 |
Log lines arrived faster than the database could take them, and some were not kept. The system log says how many. | A burst of requests (a script, a crawler, many readers at once) on slow storage. Request and file lines are dropped first; job lines and errors last. | Nothing, if it is rare. If it is frequent, check the storage the data volume sits on. |
TIP-LOG-004 |
The prune that removes jobs and log lines older than 30 days failed. | The database was busy or read-only at the time. | Nothing is lost; old lines stay until the next prune succeeds, which is tried again once a log line is written an hour or more after the failed one began, or at once when Settings → Jobs opens (its first read of the job list asks for one, /api/jobs?view=past&prune=1). |
TIP-LOG-005 |
At shutdown, the log lines still waiting to be written when the flush's budget ran out were not kept in the database. | The database's write lock was held, or the storage was slow, as the server stopped. Shutdown gives the log one second, or less if the stop has already used most of Docker's ten-second grace, so the whole stop fits inside it. | Nothing to fix. The lines are still in docker logs; only the database's copy of the last second, the one Settings → Jobs shows, is missing them. |
TIP-JOBS-001 |
A job stopped on an internal error. It was marked failed, its log kept, and the next job in the queue started. | A bug in Tippani, set off by something in the item the job was working on. The [error] line carries the stack. |
Please report the full [error] line. Whoever started the job can run it again from Settings → Jobs (POST /api/jobs/<id>/rerun); if it fails the same way on the same item, leave that item out. |
TIP-JOBS-002 |
A job's state, progress or result could not be written to the database, so Settings → Jobs (and /api/jobs/<id>) may show it out of date. A job's start and end are retried for about half a minute first; when its end still could not be written, the line says so and names how the job actually ended. Or, when the line says the database was not swapped: a restore or a factory reset was refused before it touched anything, or a search rebuild's recovery of the whole database was, once the indexes that could be rebuilt in place had been (the rebuild then lists the rest as failed), because a waiting job somebody had stopped could not first be written stopped in the database it was about to replace. |
Another writer held the database's write lock through every retry (a long import, a sqlite3 shell, slow storage), the disk is full, or the database is damaged (TIP-STORE-002). |
Free disk space if it is short. A job shown as running after its work ended stays that way until Stop (or Stop all) is pressed on it in Settings → Jobs (POST /api/jobs/<id>/stop, POST /api/jobs/stop-all), which marks it interrupted at once, or until the next restart does; either way whoever started it can run it again. A refused restore, reset or rebuild left the library as it was: close whatever else has the database open, then press it again. |
TIP-JOBS-003 |
Settings → Jobs could not read the job history, a job's log or the system log (/api/jobs…, /api/admin/logs…), and answered "internal error"; or a Markdown export stopped before its end. |
The database was busy or damaged (TIP-STORE-002) while the list or the log was read. An export also stops early when the download is given up: the browser closed it, or it took nothing for a minute (a phone asleep mid-download). And a restore or a factory reset that replaces the database part-way through an export ends it, with a last line inside the file saying so. |
Reload Settings → Jobs. If it happens every time, look for TIP-STORE-002 in docker logs and follow that row. An export that stopped early can be downloaded again. |
These pages are the documentation itself, not a summary of it — they live in
docs/wiki/ and nowhere else.
Report anything wrong.
The code
Running it
The parts
Generated — not wiki pages