本文へ移動
cccskills
無料GitHub で公開

beam-elixir-ast-gotchas

Fix Elixir/Erlang AST processing bugs in Grafema beam-analyzer. Use when: (1) Elixir parser returns MODULE node but 0 functions/calls — body nesting issue, (2) Erlang parser crashes with "cannot convert list to string" on OTP 26+ — location format changed from integer to keyword list, (3) pipe operator |> creates spurious CALL nodes instead of desugared function calls — clause ordering bug, (4) multi-module .ex files return only the first module — missing __block__ handler, (5) installing Erlang/Elixir on macOS with outdated Xcode/Clang.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md5.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

BEAM/Elixir AST Processing Gotchas

Problem

When building tools that process Elixir/Erlang ASTs (like Grafema's beam-analyzer), several non-obvious AST structural issues cause silent failures or crashes.

Context / Trigger Conditions

Gotcha 1: Elixir AST Body Double-Nesting

  • Symptom: MODULE node created correctly, but 0 FUNCTION/CALL/VARIABLE nodes
  • Trigger: Any Elixir file with functions inside a module
  • Root cause: {:defmodule, meta, [alias | body]} produces body = [[do: ...]], NOT body = [do: ...]. The body is wrapped in an extra list layer.
  • Same issue for functions: {:def, meta, [{name, _, args} | body]} also gives body = [[do: ...]]

Fix: Always unwrap with List.first(body) || [] before pattern matching on [do: ...]:

# WRONG — body is [[do: ...]], not [do: ...]
walk_module_body(body, ctx)

# RIGHT
keyword_body = List.first(body) || []
walk_module_body(keyword_body, ctx)

Gotcha 2: OTP 26+ Erlang AST Location Format

  • Symptom: (ArgumentError) cannot convert the given list to a string
  • Trigger: Processing Erlang .erl files with OTP 26 or newer
  • Root cause: OTP 26 changed Erlang abstract forms from {:attribute, LineNumber, ...} to {:attribute, [text: ~c"...", location: N], ...}. The second element is now a keyword list, not an integer.

Fix: Add a location extractor helper:

defp extract_line(loc) when is_integer(loc), do: loc
defp extract_line(loc) when is_list(loc), do: Keyword.get(loc, :location, 0)
defp extract_line(_), do: 0

Apply to ALL Erlang form handlers: :module, :export, :function, :type, :spec, :import, :call, :match, :case, etc.

Gotcha 3: Pipe Operator Clause Ordering

  • Symptom: CALL nodes with name |> instead of desugared function names
  • Trigger: Elixir pipe chains like data |> String.trim() |> Enum.map(...)
  • Root cause: {:|>, meta, [left, right]} matches the general clause {name, meta, args} when is_atom(name) and is_list(args) because :|> is an atom. If the general clause is defined before the pipe-specific clause, it catches pipes first.

Fix: Always define the {:|>, ...} clause BEFORE {name, meta, args}:

# Pipe clause MUST come first
defp walk_pipe_arg({:|>, _meta, [left, right]}, ctx) do ...
defp walk_pipe_arg({{:., _, _}, meta, args}, ctx) do ...    # dot calls
defp walk_pipe_arg({name, meta, args}, ctx) when is_atom(name) do ...  # general - LAST

Gotcha 4: Multi-Module Files

  • Symptom: Only first module processed, or 0 nodes if top-level is __block__
  • Trigger: Elixir files with multiple defmodule at the top level
  • Root cause: Code.string_to_quoted returns {:__block__, _, [defmodule1, defmodule2, ...]} for multi-module files, but {:defmodule, ...} for single-module files.

Fix: Handle both in the walker:

defp walk_elixir({:__block__, _, statements}, ctx) do
  Enum.reduce(statements, ctx, fn stmt, ctx -> Rules.Modules.process(stmt, ctx) end)
end
defp walk_elixir(ast, ctx), do: Rules.Modules.process(ast, ctx)

Gotcha 5: ::: Atom Quoting Warning

  • Symptom: Elixir 1.16+ warning: atom ::: must be written between quotes
  • Trigger: Pattern matching on typespec AST {:::, _, [...]}
  • Fix: Use {:"::", _, [...]} instead of {:::, _, [...]}

Verification

  • Run mix run verify.exs on comprehensive fixture files
  • Check that all expected node types appear (MODULE, FUNCTION, CALL, VARIABLE, etc.)
  • Verify Erlang files produce nodes without crashes
  • Verify pipe chains produce individual CALL nodes per function

Notes

  • The Elixir AST with columns: true, token_metadata: true options adds extra metadata to every node — be aware when pattern matching
  • OTP version detection: check :erlang.system_info(:otp_release) if you need conditional handling
  • For debugging AST structure, use Code.string_to_quoted(source) |> IO.inspect(pretty: true)

Installing Erlang/Elixir on macOS with Outdated Clang

If brew install erlang fails with a clang crash on PCRE2 (Clang 14.x):

  1. Install kerl: brew install kerl
  2. Build with problematic deps disabled:
    KERL_CONFIGURE_OPTIONS="--without-wx --without-odbc --without-javac --disable-jit" \
      kerl build 26.2.5 26.2.5
    kerl install 26.2.5 ~/.kerl/installs/26.2.5
    source ~/.kerl/installs/26.2.5/activate
    
  3. Install Elixir from pre-built binary (match OTP version):
    curl -fsSL https://github.com/elixir-lang/elixir/releases/download/v1.16.3/elixir-otp-26.zip \
      -o /tmp/elixir.zip
    unzip -q /tmp/elixir.zip -d ~/.elixir
    export PATH="~/.kerl/installs/26.2.5/bin:~/.elixir/bin:$PATH"
    

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Fires after fixing any non-trivial bug or regression. Asks: could the graph have caught this as a guarantee? Pairs with reflection-in-and-on-action — picks up after "earliest catchable signal" and asks the next question: was that signal expressible in graph? Triggers: (1) after any non-trivial bug fix is verified working, (2) after a regression report (something used to work, broke), (3) during step 6 (knowledge extraction) of the workflow, (4) when reflection-on-action surfaces a "would have been catchable" signal. Outcome is a triage decision (graph-reachable? rule expressible? rule sound?) and either a draft Linear issue + guarantee proposal, or a recorded "graph capability gap" note. Never auto-creates guarantees.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix Playwright automation failures against code-server (VS Code in browser). Use when: (1) trust dialog blocks all clicks — "monaco-dialog-modal-block intercepts pointer events", (2) button:has-text() finds wrong buttons behind modal dialog, (3) keyboard shortcuts don't work — Meta vs Control inconsistency, (4) VS Code extension activity bar icon not found by aria-label, (5) panels show placeholder text despite extension being "Connected". Covers trust dialog dismissal, keyboard shortcut hybrid mode, extension panel selectors, and database connection verification for Grafema extension.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Systematic methodology for achieving 100% backward dataflow reachability in a new language. Create gauntlet fixture, write trace, diagnose gaps, fix analyzer/algorithm, iterate to 100%. Language-agnostic process. Use when: (1) adding a new language to Grafema, (2) auditing dataflow coverage for existing language, (3) user says "/dataflow-gauntlet".

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix docker exec hanging when starting background processes (servers, daemons) inside containers. Use when: (1) docker exec never returns despite using & or nohup, (2) background server started in container causes docker exec to hang indefinitely, (3) env_startup_command in SWE-bench or similar frameworks times out, (4) setsid/disown needed for proper process detachment in Docker. Root cause: docker exec tracks ALL processes in the exec session, not just the top-level PID.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix Node.js CLI tools crashing inside Docker containers when host-installed node_modules require a newer Node version than the container provides. Use when: (1) "SyntaxError: Invalid regular expression flags" with /v flag in string-width or similar packages, (2) node_modules installed on host with Node 20+ but container has Node 18, (3) `npm install` with file: protocol creates symlinks that break inside Docker, (4) pnpm workspace packages become broken symlinks in containers. Covers version detection, Node binary mounting, and npm install strategies for cross-version compatibility.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Fix intermittent `{:no_translation, :unicode, :latin1}` crashes in Elixir escript daemons that use length-prefixed framed IPC on stdin/stdout. Use when: (1) daemon worker crashes only on some input files, usually ones with non-ASCII bytes (kanji, cyrillic, emoji); (2) error surfaces as `Protocol error` from daemon's error branch or as garbled frame-length bytes seen by the orchestrator/client side; (3) standalone one-shot mode works fine on the same input but multi-request daemon mode fails; (4) `IO.binread(:stdio, N)` returns `{:error, {:no_translation, :unicode, :latin1}}` despite the "bin" prefix suggesting it should be encoding-agnostic. Root cause is the escript default `:standard_io` encoding — it's `:unicode`, and `IO.binread` still routes through the io_server, which translates bytes to codepoints and errors when raw binary frames contain invalid UTF-8 sequences.

日本語の概要は準備中です。原文の説明を表示しています。

Disentinel/grafema362026年8月24日 更新

Disentinel のスキルをすべて見る

このスキルの問題を報告する