DEV Community

bigkijimon
bigkijimon

Posted on • Edited on

graphify broke completely on Apple Silicon — the x86_64 tree-sitter trap, and graphing an entire codebase + docs into a knowledge graph

Originally published on Zenn (Japanese). Cross-posted here.

The problem where Claude Code re-reads files every time

When you work on a largish project with Claude Code, the same thing happens every session. "Where was that file again" → grep, Read the whole thing, Read another file… The codebase is too big for grep and too expensive to load whole into context.

What solves this is graphify (the PyPI package name is graphifyy with a doubled y; the CLI command is graphify). It turns an entire folder into a knowledge graph, and instead of Claude re-reading files, it queries the graph. It claims "up to 71.5× token reduction per query."

What a knowledge graph is, in one line. It parses code and docs and extracts them as a directed graph of "functions / classes / concepts = nodes" and "calls / depends on / references = directed edges." When you ask a question, instead of re-reading everything it returns only the relevant nodes as a subgraph. That's why it's light. graphify parses code locally with tree-sitter (AST, no LLM), and uses a model for semantic extraction only on the docs.

On an M1 Max (Apple Silicon), I tried to graph my own project — a set of shell orchestrators, a Swift menu-bar app, and design docs — all at once. And it broke completely, right away. This article is that record.

Troubleshooting

Total failure: incompatible architecture

I installed it with uv tool install graphifyy, ran AST extraction, and the moment I did, this appeared.

ImportError: dlopen(.../tree_sitter/_binding.cpython-313-darwin.so, 0x0002):
  tried: '.../tree_sitter/_binding.cpython-313-darwin.so'
  (mach-o file, but is an incompatible architecture
   (have 'x86_64', need 'arm64e' or 'arm64'))
Enter fullscreen mode Exit fullscreen mode

The native binding (.so) for tree_sitter is x86_64, and arm64 Python can't load it. graphify parses code with tree-sitter's AST, so if this dies, code extraction dies entirely.

A classic Apple Silicon issue, but the tricky part was that it was "an x86_64 dependency graphify itself carried." Not my code, not a model — the tool's native dependency was installed assuming x86_64. The cause is simple: arm64 Python can't dlopen an x86_64 .so and crashes. uv tool's resolution had grabbed an x86_64 wheel.

Fix ①: reinstall tree-sitter for arm64

The uv tool environment has no pip, so you force a reinstall by targeting that environment with uv pip.

PY=/Users/you/.local/share/uv/tools/graphifyy/bin/python
uv pip install --python "$PY" --reinstall-package tree-sitter --no-cache 'tree-sitter>=0.23.0'
Enter fullscreen mode Exit fullscreen mode

tree-sitter 0.26.0 (arm64) went in, and the dlopen screaming stopped. …but the AST result was still 0 nodes.

Fix ②: the language grammars simply weren't installed

On retry, this came up next.

warning: 32 .swift file(s) contributed nothing to the graph
  because a dependency is missing: tree_sitter_swift not installed.
Enter fullscreen mode Exit fullscreen mode

The tree-sitter core was fixed, but each language's grammar (tree_sitter_swift, etc.) is a separate package and wasn't installed. Install them all at once here. In particular, installing tree-sitter-bash lets you AST-parse the shell orchestration set too — which was the real prize in my environment.

uv pip install --python "$PY" --no-cache \
  tree-sitter-bash tree-sitter-swift tree-sitter-python \
  tree-sitter-typescript tree-sitter-javascript tree-sitter-json tree-sitter-go
Enter fullscreen mode Exit fullscreen mode

All seven languages import OK. This tree-sitter-bash install was what did it: the 13 shell scripts and 32 Swift files that had been 0 nodes became parseable. Re-retry:

Swift AST: 323 nodes, 759 edges
Enter fullscreen mode Exit fullscreen mode

It worked. Fix the arch, install the grammars — it comes back in these two stages. With only one of them it goes silent at 0 nodes, so both are required. The overall flow looks like this.

From total failure to recovery — graphify on Apple Silicon, a two-stage fix (arch error → arm64 reinstall → still 0 nodes → install language grammars → from 0 to 332 nodes)

Stage State
Initial Total failure at dlopen incompatible architecture
After fix ① (arm64 tree-sitter) Arch error gone, but 0 nodes
After fix ② (language grammars) 332 nodes / 653 edges

Building the code graph: bash + Swift in one graph

Code → 332 nodes

First the code assets via AST (no LLM, completely free). 13 shell orchestrators (GPU arbitration, dispatcher, monitoring, etc.) + 32 Swift app files = 45 files in one graph.

Graph: 332 nodes, 653 edges, 23 communities
God node: core ViewModel   ← correctly detects the app's hub
Enter fullscreen mode Exit fullscreen mode

One best practice here. Exclude tests, include infra/docs. Tests bloat the node count 3–4× and muddy the clustering (a rule of thumb noted even in graphify's official guide). Conversely, infra config and docs tell you "how the system is wired (what depends on what)."

+ docs → 629 nodes

Next I added 60 docs — CLAUDE.md and design docs — to the graph via semantic extraction (parallel subagents). Result:

ENRICHED Graph: 629 nodes, 986 edges, 76 communities
Enter fullscreen mode Exit fullscreen mode

Code structure and design knowledge now co-reside in one graph. Diagrammed, the build flow looks like this.

Code + docs → one knowledge graph → byproducts (45 code files via local AST, free; 60 design docs via semantic extraction. From the 629-node/986-edge graph come graph.html, Obsidian, Wiki, MCP server, and graphify query)

Actually querying it

Ask "what is the GPU-arbitration area connected to?" and it returns this via BFS (identifiers genericized; format is as-is from real output).

$ graphify query "gpu arbitration freeze thaw"
Traversal: BFS depth=2 | Start: [GpuMonitor] | 13 nodes found

NODE GpuMonitor       [src=app/services/gpu_monitor.swift  L9   community=8]
NODE MemSwitchScript  [src=scripts/mem_switch.sh           L1   community=29]
NODE thaw_ollama()    [src=scripts/mem_switch.sh           L38  community=29]
NODE LocalLLMRuntime  [src=docs/env_snapshot.md            —    community=6]

EDGE GpuMonitor      --shares_data_with--> CoreViewModel   [INFERRED  0.85]
EDGE MemSwitchScript --references-->       LocalLLMRuntime [EXTRACTED 1.0]
EDGE thaw_ollama()   --calls-->            LocalLLMRuntime [EXTRACTED 1.0]
Enter fullscreen mode Exit fullscreen mode

A Swift type (code), a shell script and function (code), and the design note's "local LLM runtime" (doc) come out mixed into a single answer. And it attaches the relationship type and confidence too, like shares_data_with (INFERRED 0.85) and references (EXTRACTED 1.0). It returns "the implementation" and "why / where it connects" at once — that's the effect of co-residing code and docs. graphify explain "<node>" lays out Type / Community / Degree and a list of connections in plain terms.

Performance measurement: ~215× token reduction

Here's the measured output of graphify benchmark.

Corpus:          31,450 words → ~41,933 tokens (naive)
Graph:           629 nodes, 986 edges
Avg query cost:  ~195 tokens
Enter fullscreen mode Exit fullscreen mode

As a table:

Method Cost per query Ratio
Load the whole corpus (naive) ~41,933 tokens
graphify query ~195 tokens ~215× reduction

(Corpus 31,450 words / graph 629 nodes, 986 edges / measured with graphify benchmark.)

There are two numbers. The claimed "up to 71.5×" is graphify's official benchmark, and my own measured value on this corpus is ~215× (the output of graphify benchmark). It varies with the corpus content and the question — the more you ask about structure and dependencies, the better it works; it doesn't help with edits themselves or with files outside the graph. But the common result is that "the tax of re-loading the codebase every time" disappears by an order of magnitude.

The byproducts are all tasty too

graphify emits a pile of artifacts in one build. Using them all pays off.

  • HTML (graph.html): an interactive graph you open in a browser. No server needed.
  • Obsidian vault: bidirectional links, one md per node. Turns into a "second brain."
  • Wiki: per-community articles. Agents navigate from wiki/index.md.
  • MCP server: python -m graphify.serve graph.json. Exposes get_node / get_neighbors / shortest_path as tools. Agents can query the graph live (requires the mcp package).
  • Always-on integration: graphify claude install puts rules and a PreToolUse hook into CLAUDE.md, so from then on it references the graph automatically before codebase questions, no explicit /graphify needed.

The generated Obsidian notes are one node = one memo, with frontmatter and [[…]] backlinks (identifiers genericized). First, the GpuMonitor note:

---
source_file: "app/services/gpu_monitor.swift"
type: "code"
community: "Community 8"
---
# GpuMonitor
## Connections
- [[CoreViewModel]] - `shares_data_with`
- [[MemSwitchScript]] - `conceptually_related_to`
Enter fullscreen mode Exit fullscreen mode

Open the connected CoreViewModel note and the reverse link is drawn automatically (bidirectional):

---
source_file: "app/models/core_view_model.swift"
type: "code"
community: "Community 1"
---
# CoreViewModel
## Connections
- [[GpuMonitor]] - `shares_data_with`
- [[MetricsProbe]] - `calls`
- [[SessionProbe]] - `calls`
Enter fullscreen mode Exit fullscreen mode

Open it in Obsidian's graph view and these bidirectional links become a node web as-is, letting you trace "which module connects to what" without reading a single line of code.

Takeaways (for anyone running graphify on Apple Silicon)

  1. Fix the arch error in two stages: reinstall tree-sitter core for arm64 → install the language grammars (tree-sitter-bash / swift / …). With only one, it goes silent at 0 nodes.
  2. Install the bash grammar: shell-centric orchestration is exactly where graphing pays off most.
  3. Cut tests, include infra/docs: the graph's stars are cluster purity and "wiring."
  4. Use the model API for doc semantics: code is local AST (free); docs/images cost tokens via model calls. If you're cost-conscious, narrow the scope (images are especially expensive via vision — and unnecessary for a dependency graph, so excluding them is the right call).

Once you're past the arch trap, graphify runs perfectly on Apple Silicon. And once you've turned things into a graph, the AI starts working with "the full context of the project in hand" — the biggest takeaway was that it's not just about speed.

Top comments (0)