Mix.env() and Mix.target() must not be called from compiled code.
Mix is a build tool — it is not part of a release. A Mix.env() call that
compiles fine in dev crashes in prod with UndefinedFunctionError. Branch on the
environment at compile time via config instead.
# BAD — crashes in a release
defmodule MyApp.Worker do
def start_link(opts) do
if Mix.env() === :prod, do: connect(opts), else: :ignore
end
end
# GOOD — config decides, code reads config
# config/prod.exs
config :my_app, connect_on_start: true
# worker.ex
defmodule MyApp.Worker do
@connect_on_start Application.compile_env(:my_app, :connect_on_start, false)
def start_link(opts) do
if @connect_on_start, do: connect(opts), else: :ignore
end
endScript files are exempt: any file ending in .exs (mix.exs, config/*.exs,
tests) runs under Mix, where Mix.env() is available and appropriate.
Mix tasks are exempt too — they only ever run under Mix, never in a release. A
file is treated as a Mix task when it contains use Mix.Task or when its path
contains an entry of :excluded_paths (default ["mix/tasks/"]).
Module attributes are still flagged. @env Mix.env() is evaluated at compile
time and does not crash a release, but it bakes the build environment into the
code invisibly — use Application.compile_env/3 with per-environment config so
the branch is auditable from config/.
test/support/*.ex files are still flagged too. They compile only for tests and
never ship in a release, but the stance is the same as for module attributes:
the environment branch belongs in config. Add "test/support/" to
:excluded_paths to opt out.
Known limitations
The module is matched by spelling — Mix.env() and Elixir.Mix.env() are
caught. A renamed alias (alias Mix, as: Build) is not resolved and escapes the
check; shadowing (alias MyApp.Mix) is likewise not resolved and would be
falsely flagged. Both spellings are pathological enough not to warrant alias
tracking here.
use Mix.Task exempts the whole file, not just the enclosing module. Being
wrong would require a file to define both a Mix task and an ordinary runtime
module — one module per file makes this moot.
Dynamic dispatch escapes the check — apply(Mix, :env, []) crashes a release
exactly like Mix.env() but is not a dot-call node and is not matched. This
is an evasion vector, not an idiom; no static check catches it without taint
analysis.