Claude Skill

trace

Use when debugging how a value or request reaches Elixir code, finding who calls a function, or planning a signature change. Builds the call tree with mix xref callers instead of reading files one by one.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download oliver-kriska-claude-elixir-phoenix-plugins_elixir-phoenix_skills_trace-9767a82.zip · 7 KB
Part of oliver-kriska/claude-elixir-phoenix — 93 skills

Install

skills CLI npx skills add https://github.com/oliver-kriska/claude-elixir-phoenix/tree/main/plugins/elixir-phoenix/skills/trace
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install oliver-kriska-claude-elixir-phoenix@llmmart
Git git clone https://github.com/oliver-kriska/claude-elixir-phoenix.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole oliver-kriska/claude-elixir-phoenix collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Call Tracing

Build call trees showing how functions are reached from entry points.

Iron Laws - Never Violate These

  1. Always use mix xref callers first - It's authoritative; grep is fallback only
  2. Stop at entry points - Controllers, LiveView callbacks, Oban workers, GenServer callbacks
  3. Track visited MFAs - Prevent infinite loops from circular calls
  4. Extract argument patterns - Just knowing "who calls" isn't enough; HOW they call matters
  5. Max depth 10 - Deeper trees indicate architectural issues, not useful traces

When to Build Call Tree (Use Proactively)

Condition Why Call Tree Helps
Unexpected nil/value at runtime Trace where the value originates
Bug can't reproduce locally See all entry points that reach the code
Changing function signature Find all callers and their argument patterns
Incomplete stack trace Get full path context
"Where does X come from?" Visual answer to data flow question

Quick Trace

Run the caller query first, then inspect another function in the chain as needed:

mix xref callers MyApp.Accounts.update_user/2
mix xref callers MyApp.Accounts.get_user/1

Read the reported locations to see argument patterns.

Entry Points (Stop Here)

Pattern Type
def mount/3, def handle_event/3 LiveView
def index/2, def show/2, def create/2 Controller
def perform(%Oban.Job{}) Oban Worker
def handle_call/3, def handle_cast/2 GenServer

Delegate to call-tracer Agent

For full recursive tree with argument extraction and parallel category tracing:

Determine the effective maximum nesting depth. Use an explicit positive-integer CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH value first; when it is unset, inspect claude --version (the default is 1 in 2.1.217–2.1.218 and 3 in 2.1.219+). If the version is unavailable, conservatively use 1. At depth 2+, delegate to the orchestrator below. At depth 1, keep orchestration in this main session: spawn the applicable controller, LiveView, worker, and internal tracing prompts directly, then merge their results. Never spawn an orchestrator that cannot delegate.

Agent(subagent_type: "phx:call-tracer", prompt: "Build call tree for MyApp.Accounts.update_user/2")

The call-tracer agent uses parallel subagents for each entry point category:

  • Controllers subagent (HTTP paths)
  • LiveView subagent (WebSocket paths)
  • Workers subagent (Background jobs)
  • Internal subagent (Cross-context calls)

Each gets a fresh context for deep exploration.

Output Location

.claude/plans/{slug}/research/call-tree-{function}.md

References

For detailed patterns:

  • ${CLAUDE_SKILL_DIR}/references/mix-xref-usage.md - Full mix xref commands and options
  • ${CLAUDE_SKILL_DIR}/references/entry-points.md - All Phoenix/OTP entry point patterns
  • ${CLAUDE_SKILL_DIR}/references/argument-extraction.md - AST parsing for argument patterns
Files (claude-elixir-phoenix)
  • references
    • argument-extraction.md 5.8 KB
      # Argument Extraction
      
      Techniques for extracting argument patterns from call sites.
      
      ## Why Arguments Matter
      
      Knowing "who calls" isn't enough. **HOW** they call reveals:
      
      - Data flow through the system
      - Where nil values originate
      - Pattern mismatches (string vs atom keys)
      - Missing validations
      
      ## Basic Extraction
      
      ### From Call Site Line
      
      ```elixir
      # Call site: lib/web/controllers/user_controller.ex:45
      Accounts.update_user(user, attrs)
      
      # Extract:
      # Arg 1: `user` - variable
      # Arg 2: `attrs` - variable
      
      # Need to trace where these variables come from in the same function
      ```
      
      ### Trace Variable Origins
      
      ```elixir
      def update(conn, %{"id" => id, "user" => user_params}) do
        user = Accounts.get_user!(id)         # <- user comes from DB query
        attrs = sanitize_params(user_params)  # <- attrs comes from params + transform
      
        case Accounts.update_user(user, attrs) do  # <- call site
          {:ok, user} -> redirect(conn, to: ~p"/users/#{user}")
          {:error, changeset} -> render(conn, :edit, changeset: changeset)
        end
      end
      
      # Full trace:
      # user = Accounts.get_user!(id) where id = params["id"] (string!)
      # attrs = sanitize_params(user_params) where user_params = params["user"]
      ```
      
      ## Common Argument Patterns
      
      ### Direct from Params (Controller)
      
      ```elixir
      def create(conn, %{"user" => user_params}) do
        Accounts.create_user(user_params)
        #                    ^^^^^^^^^^^
        # Source: conn.params["user"] (STRING KEYS!)
      end
      ```
      
      ### From Socket Assigns (LiveView)
      
      ```elixir
      def handle_event("save", %{"user" => params}, socket) do
        Accounts.update_user(socket.assigns.current_user, params)
        #                    ^^^^^^^^^^^^^^^^^^^^^^^^^   ^^^^^^
        # Source 1: socket.assigns (set in mount)
        # Source 2: event params (STRING KEYS from form!)
      end
      ```
      
      ### From Job Args (Oban)
      
      ```elixir
      def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
        user = Accounts.get_user!(user_id)
        Accounts.sync_user(user)
        #                  ^^^^
        # Source: DB query using job.args["user_id"]
      end
      ```
      
      ### Piped/Transformed
      
      ```elixir
      users
      |> Enum.map(&Accounts.update_user(&1, %{status: :active}))
      #                                 ^^
      # Source: element from `users` list (need to trace where users comes from)
      ```
      
      ## AST-Based Extraction (Advanced)
      
      Using Sourceror for precise extraction:
      
      ```elixir
      defmodule ArgumentExtractor do
        def extract_call_args(file_path, line, {target_mod, target_fun, _arity}) do
          file_path
          |> File.read!()
          |> Sourceror.parse_string!()
          |> find_call_at_line(line, target_mod, target_fun)
          |> extract_args()
        end
      
        defp find_call_at_line(ast, target_line, target_mod, target_fun) do
          {_ast, result} = Macro.prewalk(ast, nil, fn
            # Remote call: Module.function(args)
            {{:., meta, [{:__aliases__, _, mod_parts}, fun_name]}, _, args} = node, acc ->
              if meta[:line] == target_line and
                 Module.concat(mod_parts) == target_mod and
                 fun_name == target_fun do
                {node, args}
              else
                {node, acc}
              end
      
            node, acc ->
              {node, acc}
          end)
      
          result
        end
      
        defp extract_args(nil), do: []
        defp extract_args(args), do: Enum.map(args, &arg_to_string/1)
      
        defp arg_to_string({var, _, nil}) when is_atom(var), do: "#{var}"
        defp arg_to_string({{:., _, [Access, :get]}, _, [base, key]}), do: "#{arg_to_string(base)}[#{inspect(key)}]"
        defp arg_to_string({:@, _, [{name, _, _}]}), do: "@#{name}"
        defp arg_to_string(literal) when is_binary(literal), do: inspect(literal)
        defp arg_to_string(literal) when is_atom(literal), do: inspect(literal)
        defp arg_to_string(literal) when is_number(literal), do: inspect(literal)
        defp arg_to_string(_), do: "<complex expression>"
      end
      ```
      
      ## Grep-Based Extraction (Simpler)
      
      When AST parsing is overkill:
      
      ```bash
      # Get the line with context
      sed -n '43,47p' lib/web/controllers/user_controller.ex
      
      # Output:
      #   user = Accounts.get_user!(id)
      #
      #   case Accounts.update_user(user, params) do
      #     {:ok, user} -> redirect(conn, to: ~p"/users/#{user}")
      ```
      
      Then parse visually or with simple regex.
      
      ## Documenting Arguments in Call Tree
      
      Format for clarity:
      
      ```markdown
      ## Call Site: lib/web/controllers/user_controller.ex:45
      
      **Call:** `Accounts.update_user(user, attrs)`
      
      **Arguments:**
      1. `user` - Variable
         - Defined at line 42: `user = Accounts.get_user!(id)`
         - Origin: Database query using `params["id"]`
      
      2. `attrs` - Variable
         - Defined at line 43: `attrs = params["user"]`
         - Origin: Request params (string keys!)
      
      **Data Flow:**
      ```
      
      HTTP Request → params["id"] → DB Query → user
      → params["user"] → attrs
      → update_user(user, attrs)
      
      ```
      ```
      
      ## Key Patterns to Flag
      
      ### String vs Atom Key Mismatch
      
      ```elixir
      # Controller receives string keys
      def update(conn, %{"user" => params}) do
        # But internal function might expect atom keys
        Accounts.update_user(user, params)  # ⚠️ params has string keys!
      end
      ```
      
      ### Nil Propagation Risk
      
      ```elixir
      # get_user returns nil on not found
      user = Accounts.get_user(id)  # might be nil!
      Accounts.update_user(user, attrs)  # ⚠️ passing nil?
      
      # vs safe version
      user = Accounts.get_user!(id)  # raises on nil
      ```
      
      ### Unvalidated External Data
      
      ```elixir
      def perform(%Oban.Job{args: args}) do
        # args comes from untrusted source (whoever enqueued the job)
        Accounts.delete_user!(args["user_id"])  # ⚠️ no validation!
      end
      ```
      
      ## Integration with Call Tracer
      
      When building call tree, for each call site:
      
      1. Read 10 lines before call site (variable definitions)
      2. Extract argument expressions from call
      3. Trace each argument to its origin
      4. Note any transformations
      5. Flag potential issues (nil, string keys, unvalidated)
      
      ```markdown
      ├─► MyAppWeb.UserController.update/2
      │   └── lib/my_app_web/controllers/user_controller.ex:45
      │       **Arguments:**
      │       - `user`: from `Accounts.get_user!(params["id"])` ✓
      │       - `attrs`: from `params["user"]` ⚠️ string keys
      ```
      
    • entry-points.md 5.5 KB
      # Entry Points Reference
      
      Patterns for identifying entry points in Elixir/Phoenix applications. These are where request/event handling begins - stop tracing here.
      
      ## Phoenix Controllers
      
      ```elixir
      # Standard REST actions
      def index(conn, _params)
      def show(conn, %{"id" => id})
      def new(conn, _params)
      def create(conn, %{"user" => user_params})
      def edit(conn, %{"id" => id})
      def update(conn, %{"id" => id, "user" => user_params})
      def delete(conn, %{"id" => id})
      
      # Custom actions
      def custom_action(conn, params)
      ```
      
      **Detection pattern:**
      
      ```regex
      def (index|show|new|create|edit|update|delete|\w+)\(conn[,\s]
      ```
      
      **Entry point info:**
      
      - Route: Check `router.ex` for matching path
      - HTTP method: GET/POST/PUT/PATCH/DELETE
      - Params come from: URL params, query string, request body
      
      ## Phoenix LiveView
      
      ```elixir
      # Lifecycle
      def mount(params, session, socket)
      def handle_params(params, uri, socket)
      def terminate(reason, socket)
      
      # Events
      def handle_event("event_name", params, socket)
      def handle_event("event_name", %{"key" => value}, socket)
      
      # Messages
      def handle_info(message, socket)
      def handle_info({:ref, data}, socket)
      def handle_info(%Phoenix.Socket.Broadcast{}, socket)
      
      # Async operations
      def handle_async(name, async_fun_result, socket)
      ```
      
      **Detection patterns:**
      
      ```regex
      def mount\(_?\w*, _?\w*, socket\)
      def handle_event\("[\w-]+", .*, socket\)
      def handle_info\(.*, socket\)
      def handle_params\(.*, .*, socket\)
      ```
      
      **Entry point info:**
      
      - mount: Initial page load, params from URL
      - handle_event: User interaction, params from JS/form
      - handle_info: PubSub messages, process messages
      - handle_params: URL changes (live_patch)
      
      ## LiveComponent
      
      ```elixir
      # Lifecycle
      def mount(socket)
      def update(assigns, socket)
      def handle_event("event", params, socket)
      ```
      
      **Note:** LiveComponents receive assigns from parent, but handle_event is an entry point for component-specific events.
      
      ## Oban Workers
      
      ```elixir
      # Standard perform
      def perform(%Oban.Job{args: args} = job)
      def perform(%Oban.Job{args: %{"user_id" => user_id}})
      
      # With meta
      def perform(%Oban.Job{args: args, meta: meta})
      ```
      
      **Detection pattern:**
      
      ```regex
      def perform\(%Oban\.Job\{
      ```
      
      **Entry point info:**
      
      - Triggered by: Oban queue processing
      - Args source: `Oban.insert(%{args: %{...}})`
      - No user context (unless passed in args)
      
      ## GenServer
      
      ```elixir
      # Synchronous calls
      def handle_call(request, from, state)
      def handle_call({:get, key}, _from, state)
      def handle_call(:status, _from, state)
      
      # Asynchronous casts
      def handle_cast(request, state)
      def handle_cast({:update, value}, state)
      
      # Info messages
      def handle_info(message, state)
      def handle_info(:tick, state)
      def handle_info({:DOWN, ref, :process, pid, reason}, state)
      
      # Init
      def init(args)
      ```
      
      **Detection patterns:**
      
      ```regex
      def handle_call\(.*, _?from, state\)
      def handle_cast\(.*, state\)
      def handle_info\(.*, state\)
      def init\(
      ```
      
      **Entry point info:**
      
      - handle_call: From `GenServer.call(pid, request)`
      - handle_cast: From `GenServer.cast(pid, request)`
      - handle_info: From `send(pid, message)` or system messages
      
      ## Plugs
      
      ```elixir
      # Module plug
      def call(conn, opts)
      def init(opts)
      
      # Function plug (in controller)
      plug :authenticate
      
      def authenticate(conn, _opts)
      ```
      
      **Detection pattern:**
      
      ```regex
      def call\(conn, opts?\)
      ```
      
      **Note:** Plugs are middleware, often not final entry points but part of the chain.
      
      ## Mix Tasks
      
      ```elixir
      def run(args)
      def run([])
      def run(["--flag", value | rest])
      ```
      
      **Detection pattern:**
      
      ```regex
      def run\(\[
      def run\(args\)
      ```
      
      **Entry point info:**
      
      - Triggered by: `mix task_name args`
      - Args: Command line arguments as list
      
      ## Phoenix Channels
      
      ```elixir
      # Join
      def join(topic, payload, socket)
      def join("room:" <> room_id, _payload, socket)
      
      # Messages
      def handle_in(event, payload, socket)
      def handle_in("new_msg", %{"body" => body}, socket)
      
      # Info
      def handle_info(message, socket)
      ```
      
      **Detection patterns:**
      
      ```regex
      def join\("[\w:]+.*, .*, socket\)
      def handle_in\("[\w_]+", .*, socket\)
      ```
      
      ## Broadway (Message Processing)
      
      ```elixir
      def handle_message(processor, message, context)
      def handle_batch(batcher, messages, batch_info, context)
      def handle_failed(messages, context)
      ```
      
      **Entry point info:**
      
      - Messages from: Kafka, RabbitMQ, SQS, etc.
      - Batch processing context
      
      ## Absinthe (GraphQL)
      
      ```elixir
      # Resolver
      def resolve(parent, args, resolution)
      def resolve(_parent, %{id: id}, _resolution)
      
      # Middleware
      def call(resolution, config)
      ```
      
      **Entry point info:**
      
      - Triggered by: GraphQL query/mutation
      - Args from: GraphQL variables
      
      ## Entry Point Detection Code
      
      ```elixir
      @entry_patterns [
        # Phoenix Controllers
        ~r/def\s+(index|show|new|create|edit|update|delete)\s*\(\s*conn/,
        ~r/def\s+\w+\s*\(\s*conn\s*,/,
      
        # LiveView
        ~r/def\s+mount\s*\([^)]*socket\s*\)/,
        ~r/def\s+handle_event\s*\("/,
        ~r/def\s+handle_info\s*\([^)]*socket\s*\)/,
        ~r/def\s+handle_params\s*\(/,
      
        # Oban
        ~r/def\s+perform\s*\(\s*%Oban\.Job/,
      
        # GenServer
        ~r/def\s+handle_call\s*\(/,
        ~r/def\s+handle_cast\s*\(/,
        ~r/def\s+handle_info\s*\([^)]*state\s*\)/,
        ~r/def\s+init\s*\(/,
      
        # Plug
        ~r/def\s+call\s*\(\s*conn\s*,\s*opts?\s*\)/,
      
        # Mix Task
        ~r/def\s+run\s*\(\s*[\[\w]/
      ]
      
      def entry_point?(line) do
        Enum.any?(@entry_patterns, &Regex.match?(&1, line))
      end
      ```
      
      ## Contextualizing Entry Points
      
      When you find an entry point, gather this context:
      
      | Entry Point Type | Find This |
      |------------------|-----------|
      | Controller | Route in `router.ex`, auth plugs |
      | LiveView | Route, on_mount hooks |
      | Oban Worker | Queue config, scheduling |
      | GenServer | How it's started, supervision tree |
      | Channel | Socket config, join conditions |
      
    • mix-xref-usage.md 4.2 KB
      # Mix Xref Usage
      
      Complete reference for using `mix xref` to trace function calls.
      
      ## Basic Commands
      
      ### Find All Callers
      
      ```bash
      # Find who calls a specific function
      mix xref callers MyApp.Accounts.update_user/2
      
      # Output format:
      # lib/my_app_web/controllers/user_controller.ex:45: MyApp.Accounts.update_user/2
      # lib/my_app_web/live/settings_live.ex:67: MyApp.Accounts.update_user/2
      ```
      
      ### Trace a File
      
      ```bash
      # Show all external calls FROM a file
      mix xref trace lib/my_app/accounts.ex
      
      # Output:
      # lib/my_app/accounts.ex:5: call Ecto.Changeset.cast/4 (runtime)
      # lib/my_app/accounts.ex:12: call MyApp.Repo.insert/1 (runtime)
      # lib/my_app/accounts.ex:20: struct MyApp.Accounts.User (export)
      ```
      
      ### Dependency Graph
      
      ```bash
      # Text format (default)
      mix xref graph
      
      # DOT format for visualization
      mix xref graph --format dot > deps.dot
      dot -Tpng deps.dot -o deps.png
      
      # JSON format (Elixir 1.19+)
      mix xref graph --format json --output deps.json
      
      # Stats only
      mix xref graph --format stats
      ```
      
      ## Dependency Types
      
      `mix xref` tracks three types of dependencies:
      
      | Type | Description | Example |
      |------|-------------|---------|
      | `compile` | Compile-time dependency (macros, module body) | `use MyMacro` |
      | `export` | Struct or public definition usage | `%User{}` |
      | `runtime` | Function calls inside functions | `Repo.get(User, id)` |
      
      ### Filter by Type
      
      ```bash
      # Only runtime dependencies (function calls)
      mix xref graph --only-runtime
      
      # Only compile dependencies (macros)
      mix xref graph --only-compile
      
      # Exclude specific type
      mix xref graph --exclude runtime
      ```
      
      ## Filtering Results
      
      ### By Source/Sink
      
      ```bash
      # Calls FROM a specific file
      mix xref graph --source lib/my_app/accounts.ex
      
      # Calls TO a specific module
      mix xref graph --sink MyApp.Repo
      
      # Combine
      mix xref graph --source lib/my_app/accounts.ex --sink MyApp.Repo
      ```
      
      ### By Label (Module Pattern)
      
      ```bash
      # Only show calls to specific modules
      mix xref graph --label MyApp.Accounts
      
      # Multiple labels
      mix xref graph --label MyApp.Accounts --label MyApp.Users
      ```
      
      ## Practical Examples
      
      ### Find All Database Calls
      
      ```bash
      # Where is Repo used?
      mix xref callers MyApp.Repo
      
      # Which files call Repo.insert?
      mix xref callers MyApp.Repo.insert/1
      mix xref callers MyApp.Repo.insert/2
      ```
      
      ### Find All Uses of a Context
      
      ```bash
      # Who uses the Accounts context?
      mix xref graph --sink MyApp.Accounts --format stats
      ```
      
      ### Check Circular Dependencies
      
      ```bash
      # Find compile-time cycles (runtime cycles like verified_routes() are benign)
      mix xref graph --format cycles --label compile
      
      # Output: No cycles found (good!)
      # Or: lib/a.ex -> lib/b.ex -> lib/a.ex (bad!)
      ```
      
      ### Analyze a Single Module
      
      ```bash
      # What does this module depend on?
      mix xref graph --source lib/my_app/accounts.ex
      
      # What depends on this module?
      mix xref graph --sink lib/my_app/accounts.ex
      ```
      
      ## Integration with Call Tracer
      
      For recursive call tree building:
      
      ```bash
      # Step 1: Find direct callers
      callers=$(mix xref callers MyApp.Target.function/2)
      
      # Step 2: For each caller, find the containing function
      # Parse: lib/path/file.ex:42: MyApp.Target.function/2
      # Extract file and line, then read to find enclosing function
      
      # Step 3: Recurse
      # For each calling function, run mix xref callers again
      ```
      
      ## Fallback: When mix xref Unavailable
      
      If not in a Mix project or xref fails:
      
      ```bash
      # Grep for function calls (less accurate)
      grep -rn "Accounts\.update_user\|update_user(" lib/ --include="*.ex" | grep -v "def update_user"
      
      # Find function definitions
      grep -rn "def update_user" lib/ --include="*.ex"
      
      # Find module usage
      grep -rn "alias.*Accounts\|MyApp\.Accounts\." lib/ --include="*.ex"
      ```
      
      ## Common Issues
      
      ### "Could not find callers"
      
      ```bash
      # Ensure project is compiled
      mix compile
      
      # Check if function exists
      mix run -e "IO.inspect MyApp.Accounts.__info__(:functions)"
      ```
      
      ### Too Many Results
      
      ```bash
      # Filter by directory
      mix xref callers MyApp.Repo.get/2 | grep "controllers"
      
      # Focus on runtime only (skip compile-time)
      mix xref graph --only-runtime --sink MyApp.Module
      ```
      
      ### Private Functions
      
      `mix xref callers` only finds calls to public functions. For private functions:
      
      ```bash
      # Grep within the module file
      grep -n "function_name" lib/my_app/module.ex
      ```
      
  • SKILL.md 3.2 KB
    ---
    name: trace
    description: "Use when debugging how a value or request reaches Elixir code, finding who calls a function, or planning a signature change. Builds the call tree with mix xref callers instead of reading files one by one."
    effort: medium
    ---
    
    # Call Tracing
    
    Build call trees showing how functions are reached from entry points.
    
    ## Iron Laws - Never Violate These
    
    1. **Always use `mix xref callers` first** - It's authoritative; grep is fallback only
    2. **Stop at entry points** - Controllers, LiveView callbacks, Oban workers, GenServer callbacks
    3. **Track visited MFAs** - Prevent infinite loops from circular calls
    4. **Extract argument patterns** - Just knowing "who calls" isn't enough; HOW they call matters
    5. **Max depth 10** - Deeper trees indicate architectural issues, not useful traces
    
    ## When to Build Call Tree (Use Proactively)
    
    | Condition | Why Call Tree Helps |
    |-----------|---------------------|
    | Unexpected nil/value at runtime | Trace where the value originates |
    | Bug can't reproduce locally | See all entry points that reach the code |
    | Changing function signature | Find all callers and their argument patterns |
    | Incomplete stack trace | Get full path context |
    | "Where does X come from?" | Visual answer to data flow question |
    
    ## Quick Trace
    
    Run the caller query first, then inspect another function in the chain as needed:
    
    ```bash
    mix xref callers MyApp.Accounts.update_user/2
    mix xref callers MyApp.Accounts.get_user/1
    ```
    
    Read the reported locations to see argument patterns.
    
    ## Entry Points (Stop Here)
    
    | Pattern | Type |
    |---------|------|
    | `def mount/3`, `def handle_event/3` | LiveView |
    | `def index/2`, `def show/2`, `def create/2` | Controller |
    | `def perform(%Oban.Job{})` | Oban Worker |
    | `def handle_call/3`, `def handle_cast/2` | GenServer |
    
    ## Delegate to call-tracer Agent
    
    For full recursive tree with argument extraction and **parallel category tracing**:
    
    Determine the effective maximum nesting depth. Use an explicit positive-integer
    `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` value first; when it is unset, inspect
    `claude --version` (the default is 1 in 2.1.217–2.1.218 and 3 in 2.1.219+).
    If the version is unavailable, conservatively use 1. At depth 2+, delegate to
    the orchestrator below. At depth 1, keep orchestration in this main session:
    spawn the applicable controller,
    LiveView, worker, and internal tracing prompts directly, then merge their
    results. Never spawn an orchestrator that cannot delegate.
    
    ```
    Agent(subagent_type: "phx:call-tracer", prompt: "Build call tree for MyApp.Accounts.update_user/2")
    ```
    
    The call-tracer agent uses **parallel subagents** for each entry point category:
    
    - Controllers subagent (HTTP paths)
    - LiveView subagent (WebSocket paths)
    - Workers subagent (Background jobs)
    - Internal subagent (Cross-context calls)
    
    Each gets a fresh context for deep exploration.
    
    ## Output Location
    
    `.claude/plans/{slug}/research/call-tree-{function}.md`
    
    ## References
    
    For detailed patterns:
    
    - `${CLAUDE_SKILL_DIR}/references/mix-xref-usage.md` - Full mix xref commands and options
    - `${CLAUDE_SKILL_DIR}/references/entry-points.md` - All Phoenix/OTP entry point patterns
    - `${CLAUDE_SKILL_DIR}/references/argument-extraction.md` - AST parsing for argument patterns
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related