mix phoenix_kit.doctor (phoenix_kit v2.13.7)

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