targets/amp/skills/elixir-idioms/SKILL.md
OTP/BEAM patterns and Elixir idioms — GenServer, Supervisor, Task, Registry, pattern matching, with chains, pipes. Use when designing processes or debugging BEAM issues.
npx skillsauth add oliver-kriska/claude-elixir-phoenix elixir-idiomsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference for writing idiomatic Elixir code with BEAM-aware patterns.
and/or/not — Never use short-circuit operators in guards (guards require boolean operands)cast/4 for user input, change/2 for internalString.to_atom(user_input) causes memory leak (atoms aren't GC'd)@external_resourceGenServer.start_link/Agent.start_link in production. Use supervision treesMix.Task.run("app.config") + Application.ensure_all_started/1, never Mix.Task.run("app.start") (boots the FULL tree: endpoint port, Oban consuming)case, then cond{:ok, _}/{:error, _} for expected errors, raise for bugsNeed patterns? → case (or function heads)
Multiple operations? → with
Boolean conditions? → cond (multiple) or if (single)
Expected failure? → {:ok, _}/{:error, _} tuples
Unexpected/bug? → raise exception (let supervisor handle)
External library? → rescue (only here!)
Need state?
├─ No → Plain functions
├─ Simple get/update → Agent or ETS
├─ Complex messages/timeouts → GenServer
└─ One-off async → Task
# Pattern match in function head
def process(%{status: :active} = user), do: activate(user)
def process(%{status: :inactive} = user), do: deactivate(user)
# with for happy path
with {:ok, user} <- get_user(id),
{:ok, order} <- create_order(user) do
{:ok, order}
end
# Task for async
Task.Supervisor.async_nolink(TaskSup, fn -> work() end)
|> Task.yield(5000) || Task.shutdown(task)
| Wrong | Right |
|-------|-------|
| length(list) == 0 | list == [] or Enum.empty?(list) |
| list ++ [item] | [item \| list] \|> Enum.reverse() |
| String.to_atom(input) | String.to_existing_atom(input) |
| spawn(fn -> log(conn) end) | ip = conn.ip; spawn(fn -> log(ip) end) |
| unless condition | if !condition (unless deprecated in 1.18) |
For detailed patterns, see:
references/pattern-matching.md - Pattern matching, guards, binary matchingreferences/otp-patterns.md - GenServer, Supervisor, Task, Registryreferences/error-handling.md - Tagged tuples, rescue, withreferences/with-and-pipes.md - When to use with and |> (idiomatic patterns)references/troubleshooting.md - Production BEAM debugging (memory, performance, crashes)references/anti-patterns.md - Common mistakes and fixesreferences/mix-tasks.md - Mix task naming, option parsing, shell outputreferences/elixir-118-features.md - Duration module, dbg improvements (1.18+)references/elixir-120-type-system.md - Gradual type checker, dynamic(), verified bugs as compile warnings (1.20+, OTP 27+)tools
Compatibility alias for the Elixir/Phoenix plugin's LiveView assigns audit. Invoke explicitly with /lv:assigns.
development
Trace Elixir call trees from entry points via mix xref. Use when debugging data flow, planning signature changes, or understanding how a bug reaches code.
tools
Compatibility alias for the Elixir/Phoenix plugin's N+1 query checker. Invoke explicitly with /ecto:n1-check.
tools
Compatibility alias for the Elixir/Phoenix plugin's Ecto constraint debugger. Invoke explicitly with /ecto:constraint-debug.