| name | elixir-no-shortcuts |
| description | BLOCKS shortcuts like modifying dialyzer.ignore or .credo.exs excludes. Enforces fixing actual problems. Use when encountering ANY error, warning, or quality tool complaint. |
Elixir No Shortcuts: Fix the Real Problem
THE IRON LAW
NEVER suppress errors. ALWAYS fix the root cause.
ABSOLUTE PROHIBITIONS
You are NEVER allowed to:
Add to dialyzer.ignore
- Not for "unknown function" errors
- Not for "pattern can never match" warnings
- Not for "no local return" issues
- Not even "temporarily"
Modify .credo.exs to disable checks
- Not adding to
disabled:list - Not adding to
excluded_paths: - Not using inline
# credo:disable-for-this-file - Not raising complexity limits
- Not adding to
Suppress compiler warnings
- Not with
@compile {:no_warn_undefined, Module} - Not with
# noqastyle comments - Not by removing
--warnings-as-errors
- Not with
Modify .gitignore to hide problems
- Not to hide accidentally created files
- Not to ignore build artifacts in wrong places
- Fix the process that created them
Comment out failing tests
- Not "just for now"
- Not "until we figure it out"
- Fix the test or fix the code
Skip quality checks
- Not removing from pre-commit hook
- Not skipping with
--no-verify - Not disabling in CI/CD
INSTEAD: FIX THE ACTUAL PROBLEM
Dialyzer Errors
When Dialyzer complains:
# BAD: Adding to dialyzer.ignore
# dialyzer.ignore
lib/my_app/accounts.ex:42:pattern_can_never_match
# GOOD: Fix with proper @spec
defmodule MyApp.Accounts do
@spec get_user(integer()) :: {:ok, User.t()} | {:error, :not_found}
def get_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
end
Common Dialyzer fixes:
- Unknown function - Add @spec or import the module
- Pattern can never match - Fix the actual pattern mismatch
- No local return - Add error handling paths
- Invalid type specification - Correct the @spec to match reality
Process:
- Read the full Dialyzer error (don't just scan it)
- Understand what Dialyzer is telling you
- Add @spec that matches what the function actually does
- If function behavior is wrong, fix the function
- Run
mix dialyzeragain to verify
Credo Warnings
When Credo complains:
# BAD: Adding to .credo.exs
{Credo.Check.Refactor.CyclomaticComplexity, max_complexity: 20}
# GOOD: Refactor to reduce complexity
# Before: Complex conditional logic
def process(data, opts) do
if opts[:validate] and opts[:transform] and not opts[:skip] do
# ... 50 lines of nested logic
end
end
# After: Extract functions
def process(data, opts) do
data
|> maybe_validate(opts)
|> maybe_transform(opts)
|> finalize()
end
defp maybe_validate(data, %{validate: true}), do: validate(data)
defp maybe_validate(data, _opts), do: data
Common Credo fixes:
- High complexity - Extract functions, use pipelines
- Long functions - Break into smaller, focused functions
- Nesting too deep - Use early returns, with statements, or guard clauses
- Modules too long - Split into multiple focused modules
- Design anti-patterns - Refactor following Elixir idioms
Process:
- Read WHY Credo is warning
- Understand the code smell it detected
- Refactor to eliminate the smell
- Run
mix credo --strictto verify
Compiler Warnings
When compiler warns:
# BAD: Suppressing warning
@compile {:no_warn_undefined, SomeModule}
# GOOD: Fix the actual issue
# If function doesn't exist - implement it
# If module doesn't exist - add dependency
# If typo - fix the typo
Test Failures
When tests fail:
# BAD: Commenting out test
# test "user can login" do
# # This is broken, will fix later
# end
# GOOD: Fix the test or the code
test "user can login" do
user = fixture(:user)
assert {:ok, session} = Accounts.authenticate(user.email, "password")
assert session.user_id == user.id
end
DETECTION CHECKLIST
Before making ANY file modification, ask:
- Am I about to modify a
.ignorefile? → STOP - Am I about to add to an
excluded:ordisabled:list? → STOP - Am I about to comment out code to make errors go away? → STOP
- Am I about to skip a quality check? → STOP
- Am I about to suppress a warning? → STOP
If ANY answer is YES → Use this skill to fix the real problem.
BANNED PHRASES
If you're about to say ANY of these phrases, STOP IMMEDIATELY and use this skill:
❌ "This is a minor warning" ❌ "This will be implemented later" ❌ "It's not related to MY changes" ❌ "These warnings are safe to ignore" ❌ "I'll add a TODO and come back to it" ❌ "This is a false positive" ❌ "The code works fine, the tool is being pedantic" ❌ "Adding to ignore is just for this one case" ❌ "This function is too complex to refactor right now" ❌ "Let's just disable this check for now"
Instead: Fix the actual problem. Now. While context is fresh.
THE DEBUGGING PROCESS
When you encounter an error:
Step 1: READ THE ERROR COMPLETELY
- Don't scan - read every word
- Note the file, line, and exact message
- Understand what tool is complaining and why
Step 2: UNDERSTAND THE ROOT CAUSE
- Why is this happening?
- What is the code actually doing vs. what it should do?
- What does the tool want me to fix?
Step 3: FIX THE CODE
- Add missing @spec annotations
- Refactor complex functions
- Fix pattern matching
- Add error handling
- Implement missing functions
Step 4: VERIFY THE FIX
- Run the tool again
- See the error is GONE (not suppressed)
- All tests still pass
RATIONALIZATIONS THAT ARE WRONG
"This is a false positive from the tool"
WRONG. The tool is almost always right. If you think it's wrong, you've misunderstood either:
- What your code does
- What the tool is checking
- The Elixir/Erlang semantics
"I'll fix this later, just need to move forward"
WRONG. "Later" never comes. Fix it now while context is fresh.
"The code works fine, Dialyzer is just being pedantic"
WRONG. Dialyzer found a type inconsistency. Your code might work NOW, but it's brittle and will break when circumstances change.
"This function is too complex to refactor right now"
WRONG. If it's too complex to refactor, it's too complex to maintain. Refactor it now or suffer forever.
"Adding to ignore is just for this one case"
WRONG. Once you start ignoring, you never stop. The ignore file grows and grows. Fix. The. Code.
"The test is flaky, I'll just comment it out"
WRONG. Flaky tests indicate real problems (race conditions, improper setup/teardown, environmental dependencies). Fix the flakiness.
"This is minor warning"
WRONG. No warning is minor. Every warning is the compiler/tool trying to tell you something important. "Minor" warnings become major bugs in production.
"This will be implemented later"
WRONG. This is TODO hell. Either implement it NOW or don't write the code at all. Placeholder implementations with "TODO: implement later" never get implemented - they become permanent technical debt.
"It's not related to MY changes"
WRONG. You touched the code, you own it. The warning appeared on your watch - fix it. "Not my problem" attitude leads to rotting codebases. Leave the code better than you found it.
"These warnings are safe to ignore"
WRONG. There is no such thing as a "safe to ignore" warning. If a warning was truly safe to ignore, the tool wouldn't emit it. Every warning has a reason - understand it and fix the code.
CONSEQUENCES OF TAKING SHORTCUTS
If you suppress instead of fix:
- Technical debt accumulates - Future you will hate past you
- Real bugs hide - The error was trying to tell you something
- Code quality degrades - Broken windows theory in action
- Team velocity slows - Every shortcut makes next feature harder
- Production failures increase - Suppressed warnings become runtime errors
The 5 minutes "saved" by adding to ignore costs 5 hours in debugging later.
ENFORCEMENT
Before modifying ANY of these files, you MUST use this skill:
dialyzer.ignore.credo.exs(specificallydisabled:orexcluded_paths:sections)- Any file containing quality check configuration
.gitignore(when hiding problems vs. proper ignore patterns)- Test files (when commenting out failing tests)
If you find yourself typing "add to ignore", STOP and fix the real issue.
EXAMPLES OF PROPER FIXES
Example 1: Dialyzer Unknown Function
# Error: Function MyApp.Repo.get/2 is undefined or private
# BAD: Add to dialyzer.ignore
# GOOD: Add proper typing
defmodule MyApp.Accounts do
alias MyApp.Repo
alias MyApp.Accounts.User
@spec get_user(integer()) :: User.t() | nil
def get_user(id) do
Repo.get(User, id)
end
end
Example 2: Credo Complexity Warning
# Warning: Cyclomatic complexity is 15 (max is 9)
# BAD: Raise max_complexity to 20
# GOOD: Refactor into pipeline
def process_order(order, user, opts) do
order
|> validate_order()
|> check_inventory()
|> apply_discounts(user)
|> calculate_shipping(opts)
|> finalize_order()
end
Example 3: Pattern Match Warning
# Warning: Pattern can never match
# BAD: Add to dialyzer.ignore
# GOOD: Fix the pattern
# Before:
def handle_result({:ok, data}), do: process(data)
def handle_result(:ok), do: :ok # This can never match!
# After:
def handle_result({:ok, data}), do: process(data)
def handle_result({:error, reason}), do: {:error, reason}
THE RULE
If a quality tool complains, it's trying to help you write better code.
Listen to it. Fix the code. Don't silence the messenger.
REMEMBER
"Every time you add to an ignore file, a production bug gets its wings."
"Technical debt isn't free - you pay interest every single day."
"Fix the code, not the tooling."
"Minor warnings become major bugs. 'Later' means never. 'Not my changes' means rotting codebase."
"You touched it, you own it. Leave the code better than you found it."