Skip to content

Database

Implementation contracts and behavior for database.

SQLite (pure Go driver) with WAL mode. The live schema is code: internal/db/db.go: the initSQL create statements plus an in-code list of tolerant ALTER TABLE migrations with one-time backfills. There are no SQL migration files.

The pool holds exactly one connection (SQLite is single-writer), so every query in the process takes its turn through one door, and no query sets a timeout. Code that holds the connection while waiting for something that also needs it therefore wedges the whole server: silently: nothing crashes, nothing errors, and a blocked goroutine writes no further log line, so the container keeps reporting itself healthy while the app has stopped working. Two rules follow. Drain and close a cursor before calling anything that touches the database (see reportBookImportStalls, whose comment records the deadlock this caused). And a stall watchdog (internal/db/stallwatch.go) probes the pool from outside every 10s; two consecutive 5s failures to acquire the connection log one db: STALLED line with the pool counters and a goroutine dump naming the holder, a reminder every 5 minutes while it lasts, and the duration on recovery. Only the probe carries a deadline: real queries are untouched, so this cannot fail a slow query. It reports to the log and nowhere else on purpose: system issues and admin pushes are written through the very connection that is stuck, so during this failure the log is the only channel that still works.

Area Tables
Accounts & sessions users, refresh_tokens, connect_tokens, devices (local hardware-id deduplication; auth_method, oidc_issuer and plex_account_id preserve session provenance), oidc_identities (unique issuer/subject and user/issuer pairs), plex_identities (unique numeric Plex account ID and one per user; email/username for display), webauthn_credentials, user_content_policies (a row makes the user a kids account: rating caps per media type in a region’s scheme, hide-unrated, hidden genre ids; cascades with the user; never for an admin)
Requests request_log (approval + season/quality/book-format/instance capture, the fulfilled arr record id in book_record_id; Chaptarr book or Lidarr album; so status survives foreign-id re-keys, catalog_provider/catalog_id retaining public source identity, match_confirmed recording a verified user choice, park_reason marking server-owned delivery/import waits, and add_failure_reason marking an approval-queue row whose automatic add already failed), book_request_waiters (shared pending book subscribers + their concrete format coverage; music rows are per-user and need none), user_request_settings, request_dispatch (durable per-format state, attempts, retry schedule, lease, canonical/native record identity), request_dispatch_locks (per-instance worker leases), tv_match_overrides (local correction/pause/default revision tombstones), request_tv_targets (source/target snapshots, mapping revisions, resumable pilot phase and unique audited repair links)
Request allowances request_quota_defaults, user_request_quotas, immutable-owner request_quota_charges and request_quota_items, request_quota_resets audit, and singleton request_quota_lock; request_dispatch.delivery_started_at persists the first potentially consequential write
Instances service_instances (encrypted keys/passwords + current/pending server-only webhook credentials + the encrypted per-Chaptarr-instance hardcover_token and connection-change hardcover_revision + per-instance media path mappings/legacy mode + the media servers’ media_server_config document), user_default_instances, user_media_library_policies (ABS per-user library choices and pending managed updates), user_instance_grants (additional per-user access grants beside the default, so one person can hold e.g. an HD and a 4K library; for Jellyfin, Emby, and Plex the grant is access eligibility), user_media_server_accounts (one row per user × media-server instance: remote id and name: on Plex the canonical email of the share: whether Cantinarr created it, manage_access, access_sync_pending, disabled_at), user_media_server_unlinks (per-user/per-instance automatic relink suppression; no remote identity retained), video_app_preferences (personal per-service iPhone/iPad overrides of video-server app defaults; cascades on user deletion), listening_app_preferences (personal iOS/Android overrides of per-instance Audiobookshelf app defaults; cascades on user deletion), arr_queue_witness (durable per-instance queue-departure completion witness; its observed_at doubles as the import-history catch-up cursor; one row per instance, ignored past 6h)
Apple TV apple_tv_devices (stable identity, address, encrypted pairing credentials, revision), apple_tv_grants (per-TV adult access; cascades on user/TV deletion)
Hardcover OAuth hardcover_connections (encrypted access/refresh-token pair, credential revision and reconnect flag), hardcover_instance_connections (one selected OAuth connection per Chaptarr instance; explicit sharing and final-link credential cleanup)
Push push_tokens (one per device), notification_prefs (account master and categories), settings.push_notification_policy (server master and allowed categories), content_alert_claims (durable new-content dedupe, 10-minute window; also counted as the 12-per-window alert storm breaker, per storm_scope; broadcast and upgrade alerts spend separate budgets, silent upgrade claims spend none)
Discord discord_notifications (durable request receipts, destination revision, outgoing payload, delivery status, attempts and retry schedule); encrypted destination settings in settings
AI access user_ai_settings (explicit personal selection), user_ai_credentials (per-provider encrypted personal API keys), user_codex_accounts (personal encrypted OpenAI OAuth authorization), shared_codex_account (singleton encrypted included authorization), user_grok_accounts / shared_grok_account (the xAI Grok OAuth equivalents); users.ai_shared_enabled stores the included-access grant, while settings stores the daily health-check switch/timestamp
AI configuration history external_setting_changes (append-only AI/MCP quality-profile/custom-format outcomes, server-held before/applied snapshots, and linked quality-profile restores), profile_change_proposals (quality-profile changes parked by external MCP agents awaiting in-app admin approval; server-held plans and drift hashes, one pending per profile)
Remediation issues (exact arr-scoped reports plus admin-only system alerts, with closure provenance, the problem_kind label on auto-detected rows: the Import Doctor’s verdict, or the pre-air season finding; book issues store the Chaptarr author/book record ids in place of TMDB/TVDB identity: and last_paged_at, the durable per-issue record of the last queue-delivered admin page, which is also the 24-hour re-page budget’s input), issue_observations (durable retry/settle clocks, baseline + compact import receipt), issue_observation_downloads (incident download IDs + arr attempt/file boundaries), issue_observation_attempts (transition audit), remediation_queue_snapshots (latest successful minimal typed snapshot), remediation_observation_failures (bounded outage timer), remediation_observation_watermarks (monotonic per-instance success/failure ordering), issue_alert_queue (owed admin pushes, held for the promotion hold-down and coalesced on delivery), agent_action_alert_queue (owed approval pushes, held and coalesced the same way), issue_messages, agent_runs, agent_steps, agent_actions (one active proposal per issue; immutable proposal + approved params; auto_rule_id attributes a standing-rule decision; target_download_id freezes the arr download a dispatched fix acted on, so a repeat of an ineffective remedy is distinguishable from a first attempt on a new release), agent_approval_rules (admin-armed standing auto-approvals keyed problem × fix × facet, with counters and self-pause state), prevention_notices (one durable row per instance × problem label Cantinarr has told an admin keeps happening: the measured counts, the raise count that caps the cooldown, and the issue that carried it; the issue is the dismissable surface, this row is the memory that decides whether to speak again and how soon). The recurrence roll-up behind it is served by the partial index idx_issues_problem_recurrence on issues
MCP OAuth oauth_clients, oauth_authorization_codes (with local/OIDC/Plex provenance), oauth_refresh_tokens
Misc settings (encrypted KV: JWT secret, push key, request policy, the outbound proxy (outbound_proxy_url, address plus credentials), the app-wide Plex client identifier and the Plex migration marker, and OIDC configuration (oidc_config, containing the encrypted client secret), oidc_tested configuration proof and oidc_sso_only policy, Plex sign-in settings (plex_auth, plex_auth_enabled), and the server_settings blob holding the management-portal URL plus the discovery row source/language preferences), tmdb_tvdb_cache (30-day TTL)

View the maintained source for this page.