mix phoenix_kit.doctor (phoenix_kit v2.13.10)

Copy Markdown View Source

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 from config :phoenix_kit, :prefix, then "public" — the same resolution mix phoenix_kit.update / --status use, 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 success mix phoenix_kit.status --exit-code exists 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

  1. Repo Detection — Can we find and start the Ecto repo?
  2. DB Connectivity — Can we execute a simple query?
  3. Pool Configuration — Pool size, checkout timeout, queue settings
  4. PgBouncer Detection — Is PgBouncer between app and PostgreSQL?
  5. Migration State — PhoenixKit version (COMMENT), schema_migrations alignment
  6. Module Schema Versions — Modules owning their own chain, vs what their code expects
  7. Schema Drift — Columns a migration should have added but the DB lacks
  8. Pending Migrations — Migration files not yet recorded in schema_migrations
  9. UUID Column Types — Detects varchar uuid columns that crash Ecto on startup
  10. UUID Primary Keys — Detects primary keys that are not the expected uuid type
  11. NULL UUIDs in FK Sources — Detects NULL uuids that cause infinite backfill loops
  12. Orphaned FK References — Detects orphaned rows behind an existing FK constraint, whether it is already VALID (a trigger-bypassed write, a bulk load, direct catalog surgery) or still NOT VALID (would block its own VALIDATE), and existing constraints still sitting NOT VALID with nothing currently blocking them — V176 validates those in place; this just tells you before it does. Two boundaries on what "checked" means here: discovery reads pg_constraint, so a relationship with no FK constraint declared at all is outside this check's scope and is not examined; and discovery matches both the owning table and the referenced table to the schema being checked (--prefix), so a FK whose referenced table lives in a different schema is outside scope too, even though the owning table itself was checked
  13. Lock Conflicts — Any blocked or long-running queries?
  14. Orphaned Connections — Idle-in-transaction or stuck connections
  15. Oban Configuration — Queues and plugins that consume pool connections
  16. Oban Cron Queues — Does every crontab worker have its queue configured?
  17. PhoenixKit Supervisor — What's running (update_mode vs full)?
  18. Child Start Order — Does the Repo start before PhoenixKit/Oban in application.ex?
  19. Update Mode — Is update_mode active?
  20. daisyUI Version — Is the host's vendored daisyUI recent enough?
  21. User Dashboard (deprecated) — Is the host still on the retired dashboard?
  22. Sitemap Discoverability — Is the sitemap actually reachable?
  23. Crawler Visibility — noindex on a production-looking host, or a staging-looking host left indexable
  24. Demo Auth Pages — Are the demo auth routes still exposed?
  25. Manifest Repair (dry-run)PhoenixKit.Migrations.Repair.verify/1 runs read-only against the generated PhoenixKit.Migrations.ExpectedSchema manifest as an additional, non-fatal check (never :fail). Passes and says so if the manifest has been removed or overridden away in this checkout.
  26. Git Hooks — is .githooks/pre-commit enabled via core.hooksPath? Only runs inside a checkout of phoenix_kit itself (.githooks/pre-commit is 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

check_cron_queues(config)

@spec check_cron_queues(keyword() | nil | term()) :: {:pass | :warn, String.t()}

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.

exit_code(results)

@spec exit_code([{String.t(), {:pass | :warn | :fail, String.t()}}]) :: 0 | 1

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".

git_hooks_verdict(map)

@spec git_hooks_verdict(map()) :: {:pass | :warn, String.t()}

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, :unknown otherwise (not a repository, git missing, anything else).
  • :hooks_path{:ok, value} | :unset (a fact) | :unknown (a gap).

  • :tracked? — whether .githooks/pre-commit exists in this checkout.
  • :shadow{:ok, path} for a leftover hook in the common hooks dir (worktrees do not have their own), :none, or :unknown.