Runs dialyzer and outputs warnings as JSON.
This task wraps dialyxir's PLT building but calls :dialyzer.run/1 directly,
transforming raw warnings to structured JSON optimized for AI code editors
(Claude Code, Cursor, etc.).
Quick Start
# Health check - see summary counts
mix dialyzer.json --quiet --summary-only | jq '.summary'
# Find real bugs (fix_hint: "code")
mix dialyzer.json --quiet | jq '.warnings[] | select(.fix_hint == "code")'
# Fix issues, then re-run to verify
mix dialyzer.json --quiet --summary-onlyWhen to Use This vs mix dialyzer
| Use Case | Command |
|---|---|
| Human reading warnings | mix dialyzer |
| AI parsing warnings | mix dialyzer.json --quiet |
| CI/CD pipelines | mix dialyzer.json --quiet --output warnings.json |
| Quick status check | mix dialyzer.json --quiet --summary-only |
Options
--quiet- Suppress non-JSON output (always use for parsing)--summary-only- Counts only, no individual warnings--group-by-warning- Group warnings by type--group-by-file- Group warnings by file path--filter-type TYPE- Only show specific types (repeatable, OR logic)--compact- JSONL output (one warning per line)--output FILE- Write to file instead of stdout--ignore-exit-status- Don't fail on warnings (exit 0)
Output Format
Full output includes metadata and summary:
{
"metadata": {
"schema_version": "1.0",
"dialyzer_version": "5.4",
"elixir_version": "1.19.4",
"otp_version": "28",
"run_at": "2026-02-02T07:00:03Z"
},
"warnings": [...],
"summary": {
"total": 5,
"skipped": 0,
"by_type": {"no_return": 2, "call": 3},
"by_fix_hint": {"code": 4, "spec": 1}
}
}Each warning object contains:
{
"file": "lib/foo.ex",
"line": 42,
"column": 5,
"function": "bar/2",
"module": "Foo",
"warning_type": "no_return",
"message": "Function has no local return",
"raw_message": "Function bar/2 has no local return.",
"fix_hint": "code"
}With --group-by-warning, warnings are grouped by type:
{"warnings": {"no_return": [...], "call": [...]}, ...}With --group-by-file, warnings are grouped by file:
{"groups": [{"file": "lib/foo.ex", "count": 3, "warnings": [...]}], ...}Fix Hint Guide
The fix_hint field tells you what action to take:
| Hint | Meaning | Action |
|---|---|---|
"code" | Likely a real bug | Fix immediately - unreachable code, impossible patterns |
"spec" | Typespec mismatch | Fix the @spec - code is probably correct |
"pattern" | Common safe-to-ignore | Often intentional - third-party behaviours |
"unknown" | Unrecognized warning | Investigate manually |
Priority order: code > spec > pattern
Common jq Recipes
# Find all code bugs
mix dialyzer.json --quiet | jq '.warnings[] | select(.fix_hint == "code")'
# Most common warning types
mix dialyzer.json --quiet | jq '.summary.by_type | to_entries | sort_by(-.value)'
# Warnings in a specific file
mix dialyzer.json --quiet | jq '.warnings[] | select(.file == "lib/my_module.ex")'
# Count warnings per file
mix dialyzer.json --quiet | jq '.warnings | group_by(.file) | map({file: .[0].file, count: length})'Exit Codes
| Code | Meaning |
|---|---|
0 | No warnings found |
2 | Warnings found |
| Other | Error occurred |
Use --ignore-exit-status to always exit 0 (useful for CI that shouldn't fail on warnings).
Tips
- Always use
--quietfor parsing - suppresses dialyzer progress output - Pipe to jq for filtering and formatting
- Use
--compactfor large warning sets or streaming - Use
--filter-typeto focus on specific warning categories - Check
fix_hintbefore fixing -"pattern"warnings are often intentional