From elixir-phoenix
Core Elixir language guidelines covering common pitfalls like list access, immutability, module organization, struct access, date/time handling, and OTP patterns. Use when writing or reviewing Elixir code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/elixir-phoenix:elixir-guidelinesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Elixir lists **do not support index based access via the access syntax**.
Elixir lists do not support index based access via the access syntax.
Never do this (invalid):
i = 0
mylist = ["blue", "green"]
mylist[i]
Instead, always use Enum.at, pattern matching, or List for index based list access:
i = 0
mylist = ["blue", "green"]
Enum.at(mylist, i)
Elixir variables are immutable, but can be rebound. For block expressions like if, case, cond, etc. you must bind the result of the expression to a variable if you want to use it. You CANNOT rebind the result inside the expression:
# INVALID: rebinding inside the `if` - the result never gets assigned
if connected?(socket) do
socket = assign(socket, :val, val)
end
# VALID: rebind the result of the `if` to a new variable
socket =
if connected?(socket) do
assign(socket, :val, val)
end
changeset[:field]) on structs as they do not implement the Access behaviour by default. For regular structs, you must access the fields directly, such as my_struct.field or use higher level APIs that are available on the struct if they exist (e.g., Ecto.Changeset.get_field/2 for changesets)Time, Date, DateTime, and Calendar interfaces. Never install additional dependencies unless asked or for date/time parsing (which you can use the date_time_parser package)String.to_atom/1 on user input (memory leak risk)is_ and should end in a question mark. Names like is_thing should be reserved for guardstry/rescue — it is an anti-pattern in Elixir. Use {:ok, result} / {:error, reason} tuples and pattern matching insteadRepo.get/2, File.read/1) and match on the result rather than bang functions (Repo.get!/2) wrapped in try/rescuerescue should only be used at true system boundaries (e.g., NIF calls, third-party libraries that raise instead of returning error tuples) where no tuple-based alternative exists{:ok, val} | {:error, reason} once, then use that everywhere# BAD: using try/rescue for control flow
try do
agent = Agents.get_agent!(id)
do_something(agent)
rescue
Ecto.NoResultsError -> :not_found
end
# GOOD: pattern matching on result tuples
case Agents.get_agent(id) do
{:ok, agent} -> do_something(agent)
{:error, :not_found} -> :not_found
end
DynamicSupervisor and Registry require names in the child spec:{DynamicSupervisor, name: YourApp.MyDynamicSup}
Then use:
DynamicSupervisor.start_child(YourApp.MyDynamicSup, child_spec)
Task.async_stream(collection, callback, options) for concurrent enumeration with back-pressure. The majority of times you will want to pass timeout: :infinity as optionmix help task_name)mix test test/my_test.exs or run all previously failed tests with mix test --failedmix deps.clean --all is almost never needed. Avoid using it unless you have good reasonstart_supervised!/1 to start processes in tests as it guarantees cleanup between testsProcess.sleep/1 and Process.alive?/1 in tests
Instead of sleeping to wait for a process to finish, always use Process.monitor/1 and assert on the DOWN message:
ref = Process.monitor(pid)
assert_receive {:DOWN, ^ref, :process, ^pid, :normal}
Instead of sleeping to synchronize before the next call, always use _ = :sys.get_state/1 to ensure the process has handled prior messages
npx claudepluginhub code0100fun/botfiles --plugin elixir-phoenixProvides reference for Elixir idioms and OTP/BEAM patterns including GenServer, Supervisor, Task, Registry, pattern matching, pipes, with chains. Use when designing processes or debugging BEAM issues.
Enforces Elixir best practices like pattern matching over if/else, pipe operator for chaining, with for sequential fallible ops, @impl true, and let-it-crash when editing .ex/.exs files.
Provides Elixir best practices and OOP-to-functional shifts: avoid unnecessary processes, use pattern matching/with for control flow, {:ok/:error} handling, behaviors/protocols for polymorphism.