Agent Skill
2/7/2026

triage-elixir-library

Elixir error handling refactoring using the Triage library. Use when working with Elixir code and users mention "triage" library or the `Triage` model. Useful for refactoring or evaluating code using `triage`. This skill provides correct API usage, refactoring patterns, and helps avoid common mistakes when transforming traditional Elixir error handling (with statements, case statements, try/rescue) into Triage's pipeline-based approach.

C
cheerfulstoic
1GitHub Stars
2Views
npx skills add cheerfulstoic/triage

SKILL.md

Nametriage-elixir-library
DescriptionElixir error handling refactoring using the Triage library. Use when working with Elixir code and users mention "triage" library or the `Triage` model. Useful for refactoring or evaluating code using `triage`. This skill provides correct API usage, refactoring patterns, and helps avoid common mistakes when transforming traditional Elixir error handling (with statements, case statements, try/rescue) into Triage's pipeline-based approach.

name: triage-elixir-library description: Elixir error handling refactoring using the Triage library. Use when working with Elixir code and users mention "triage" library or the Triage model. Useful for refactoring or evaluating code using triage. This skill provides correct API usage, refactoring patterns, and helps avoid common mistakes when transforming traditional Elixir error handling (with statements, case statements, try/rescue) into Triage's pipeline-based approach.

Triage Refactoring Assistant

Expert guidance for refactoring Elixir code to use the Triage library - a lightweight error handling library that provides pipeline-based result handling with context wrapping and logging.

Core Triage Concepts

Result Types

Triage works with standard Elixir results:

  • :ok and {:ok, value}
  • :error and {:error, reason}

Some functions take {:ok, ...} / {:error, ...} tuples with more than two elements, but the philosophy of Triage is to reduce :ok / :error tuples to two elements as much as possible.

Key Functions Overview

Best practice is to never import Triage but always to refer to Triage.function.

Control Flow:

  • Triage.ok_then!/2 - Handling :ok results, allows exceptions to raise
  • Triage.ok_then/2 - Handling :ok results, catches exceptions and returns {:error, Triage.WrappedError.t()}
  • Triage.ok_then/1 - Executes callback function, catches exceptions and returns {:error, Triage.WrappedError.t()}
  • Triage.error_then/2 - error_then :error results

VERY IMPORTANT NOTE ABOUT THE ok_then!/2, ok_then/2, and error_then/2 FUNCTIONS

The first argument to these functions must always be a result (:ok, :error, {:ok, ...}, {:error, ...})

The second argument is always a function which is given the value/reason of the :ok/:errortuple. If the callback took_thenreturns something other than a result, it automatically wraps with{:ok, _}. If the callback to error_thenreturns something other than a result, it automaticalyl wraps with{:error, _}`

|> Triage.ok_then!(& &1 + 1)
# If given `:error` result as first argument, the callback isn't executed and the argument is returned
# If given `{:ok, 2}`, returns `{:ok, 3}`

|> Triage.error_then(fn _ -> :operation_failed end)
# If given `:ok` result as first argument, the callback isn't executed and the argument is returned
# If given `{:error, :something}` returns `{:error, :operation_failed}

If the callback to ok_then functions returns an :error result, the :error result is returned without wrapping in {:ok, _}

If the callback to error_then functions returns an :ok result, the :ok result is returned without wrapping in {:error, _}

Context & Debugging

Triage.wrap_context/2,3

Returns a Triage.WrappedContext.t() which stores the original error with a context string and/or metadata

|> Triage.wrap_context("querying user service")
# or...
|> Triage.wrap_context("querying user service", %{user_id: user_id})
# or...
|> Triage.wrap_context(%{user_id: user_id})

Triage.log/1,2

Log results with file/line info. Works especially well when given error tuples with Triage.WrappedError.t() values.

Second argument default to :errors, but can be :all to log :ok results as well.

Triage.user_message/1,2

Takes in an error result ({:error, _}). Extracts user-friendly error messages for returning in HTML/JSON/etc...

If a user-friendly error message can't be made from the error result, will log technical details about the error and provide a unique code for the user to pass onto support to find that log line.

Enumeration

Triage.map_if/2 - Map with error short-circuiting

Triage.find_value/2 - Find first success

Triage.all/1 - Verify all are successful

Important to know

with clauses

It may be tempting to replace all with statements which have {:ok, _} clause patterns, but it's not always a good fit.

Simplifying functions

Sometimes using triage can lead to a function being simplified to a single function call. Often it makes code simpler to remove the function if this happens.

Example:

  with {:ok, nouns} <- cue_transactions(transactions),

  # ... further down ...

  defp cue_transactions(transactions) do
    Enum.reduce_while(transactions, [], fn tx, acc ->
      case cue_transaction(tx) do
        {:ok, noun} ->
          {:cont, acc ++ [noun]}

        {:error, :cue_failed, err} ->
          {:halt, {:error, :cue_failed, err}}
      end
    end)
    |> case do
      {:error, {:cue_failed, err}} ->
        {:error, {:cue_failed, err}}

      txs ->
        {:ok, txs}
    end
  end

Could be simplified to just:

transactions
|> Triage.map_if(&cue_transaction/1)

Handling errors

with ... do
   # ...
else
   {:error, {:cue_failed, err}} ->
      {:error, {:invalid_input, err}}

   {:error, :noun_not_a_valid_transaction} ->
      {:error, :noun_not_a_valid_transaction}

   {:error, :not_enough_transactions} ->
      {:error, :not_enough_transactions}
end

In order to keep the behavior the same you need to preserve that MatchError that might happen on any unspecified errors. So instead of this:

|> Triage.error_then(fn
   {:cue_failed, err} -> {:invalid_input, err}
   reason -> reason
end)

You would do this:

|> Triage.error_then(fn
   {:cue_failed, err} -> {:invalid_input, err}
   :noun_not_a_valid_transaction -> :noun_not_a_valid_transaction
   :not_enough_transactions -> :not_enough_transactions
end)

Critical Rules to Remember

When ok_then / ok_then! receives :ok as an error result, it passes nil to the callback.

When error_then receives :error as an error result, it passes nil to the callback.

Don't always use ok_then (not ok_then!) when function might raise. Sometimes we want to raise an exception if the process should crash in that situation. Default to ok_then! functions, especially when refactoring as it's the same behavior as before.

Context is cumulative: Each wrap_context adds to the error context chain which adds details for Triage.log and Triage.user_message.

Be careful about using wrap_context: refactoring generally needs to be done at a higher level to support the Triage.WrapError.t() value.

Verification Steps

After refactoring:

  1. Ensure all result types are standard (:ok, {:ok, ...}, :error, {:error, ...})
  2. Verify exception handling requirements (use ok_then vs ok_then! appropriately)
  3. Test error paths to ensure proper propagation
  4. Check for double-wrapping anti-pattern
Skills Info
Original Name:triage-elixir-libraryAuthor:cheerfulstoic