Diagnoses PhoenixKit installation, migration, and runtime issues.
Runs a comprehensive suite of checks covering database connectivity, pool configuration, PgBouncer detection, migration state, lock conflicts, and application configuration. Prints a clear pass/fail report with actionable remediation steps.
Usage
$ mix phoenix_kit.doctor
$ mix phoenix_kit.doctor --prefix=auth
$ mix phoenix_kit.doctor --exit-code
Options
--prefix- Database schema prefix. When omitted, resolves fromconfig :phoenix_kit, :prefix, then"public"— the same resolutionmix phoenix_kit.update/--statususe, so a prefixed install is diagnosed against the schema it actually lives in.--exit-code- Exit non-zero when any check FAILED. Without it this task prints "N failures" and still exits 0, so a deploy script that runs it cannot act on the result — the same silent successmix phoenix_kit.status --exit-codeexists to remove. Warnings never fail the run; they are advisory by construction and several fire on healthy installs. Off by default so deploys that run this purely for its report keep passing.
Checks Performed
- Repo Detection — Can we find and start the Ecto repo?
- DB Connectivity — Can we execute a simple query?
- Pool Configuration — Pool size, checkout timeout, queue settings
- PgBouncer Detection — Is PgBouncer between app and PostgreSQL?
- Migration State — PhoenixKit version (COMMENT), schema_migrations alignment
- Module Schema Versions — Modules owning their own chain, vs what their code expects
- Schema Drift — Columns a migration should have added but the DB lacks
- Pending Migrations — Migration files not yet recorded in schema_migrations
- UUID Column Types — Detects varchar uuid columns that crash Ecto on startup
- UUID Primary Keys — Detects primary keys that are not the expected uuid type
- NULL UUIDs in FK Sources — Detects NULL uuids that cause infinite backfill loops
- Orphaned FK References — Detects orphaned rows (blocks VALIDATE on an existing NOT VALID constraint, or creation if the constraint is absent entirely) and existing constraints still sitting NOT VALID with nothing currently blocking them — V176 validates those in place; this just tells you before it does
- Lock Conflicts — Any blocked or long-running queries?
- Orphaned Connections — Idle-in-transaction or stuck connections
- Oban Configuration — Queues and plugins that consume pool connections
- Oban Cron Queues — Does every crontab worker have its queue configured?
- PhoenixKit Supervisor — What's running (update_mode vs full)?
- Child Start Order — Does the Repo start before PhoenixKit/Oban in application.ex?
- Update Mode — Is update_mode active?
- daisyUI Version — Is the host's vendored daisyUI recent enough?
- User Dashboard (deprecated) — Is the host still on the retired dashboard?
- Sitemap Discoverability — Is the sitemap actually reachable?
- Crawler Visibility — noindex on a production-looking host, or a staging-looking host left indexable
- Demo Auth Pages — Are the demo auth routes still exposed?
- Manifest Repair (dry-run) —
PhoenixKit.Migrations.Repair.verify/1runs read-only against the generatedPhoenixKit.Migrations.ExpectedSchemamanifest as an additional, non-fatal check (never:fail). Passes and says so if the manifest has been removed or overridden away in this checkout. - Git Hooks — is
.githooks/pre-commitenabled viacore.hooksPath? Only runs inside a checkout of phoenix_kit itself (.githooks/pre-commitis a phoenix_kit-repo convention, not something installed into a consuming host app) — silently skipped otherwise.
Summary
Functions
Reports crontab entries whose queue this node does not run.
The process exit status --exit-code should produce: 1 when any check
failed, 0 otherwise.
The "Git Hooks" verdict, as a pure function of what could actually be observed.
Functions
Reports crontab entries whose queue this node does not run.
Public so it can be unit-tested directly against config keyword lists, for
the same reason as exit_code/1: it is the pure decision inside a task whose
run/1 needs a live app and a database.
The process exit status --exit-code should produce: 1 when any check
failed, 0 otherwise.
Public because run/1 is not a unit-test seam (it starts the app and needs a
real database) — this is the pure decision behind the flag, in the same shape
as Mix.Tasks.PhoenixKit.Status.exit_code/2 and
Mix.Tasks.PhoenixKit.Repair.exit_code/1.
Only :fail gates. A :warn is advisory by construction — several fire on
perfectly healthy installs (a capped pool under update_mode, an unreadable
application.ex) — and gating on them would make the flag unusable, which is
how a task ends up back at "reports a problem and exits 0".
The "Git Hooks" verdict, as a pure function of what could actually be observed.
Public for the same reason exit_code/1 is: run/1 is not a unit-test seam,
so the decision is tested on its own.
The point of the three-way inputs is that this check must be able to say "I could not tell" instead of guessing. A check that reports "hook not installed" when it merely failed to look is worse than no check: it is confidently wrong, and it sends the reader to fix something that is not broken.
That distinction is not free, and the obvious implementation gets it wrong:
git config --get core.hooksPath exits 1 both when the key is unset and
when the current directory is not a git repository at all (verified, not
assumed). So repository-ness is probed separately, and only inside a
repository is exit 1 read as the fact "not configured".
:repo—{:ok, common_dir}when git answered,:unknownotherwise (not a repository, git missing, anything else).:hooks_path—{:ok, value}|:unset(a fact) |:unknown(a gap).:tracked?— whether.githooks/pre-commitexists in this checkout.:shadow—{:ok, path}for a leftover hook in the common hooks dir (worktrees do not have their own),:none, or:unknown.