Skip to content

fix(graph): stop graph-on-stop hook crashing when tree-sitter is absent#321

Merged
efenocchi merged 1 commit into
mainfrom
fix/graph-on-stop-lazy-treesitter
Jul 18, 2026
Merged

fix(graph): stop graph-on-stop hook crashing when tree-sitter is absent#321
efenocchi merged 1 commit into
mainfrom
fix/graph-on-stop-lazy-treesitter

Conversation

@efenocchi

@efenocchi efenocchi commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Problem

The graph-on-stop Stop/SessionEnd hook (claude-code, codex, cursor, hermes) crashes with exit code 1 on installs where the optional tree-sitter native grammar failed to build (Node 24 / arm64):

Stop hook (failed) — hook exited with code 1
ERR_MODULE_NOT_FOUND: Cannot find package 'tree-sitter'

Root cause: the hook's build path is a chain of static ESM importsrunBuildCommandextract/indeximport Parser from "tree-sitter". esbuild hoists the external tree-sitter import to the top of the bundle, so Node resolves it at module load — before main()'s try/catch runs. main().catch(() => process.exit(0)) never fires. This is still reproducible on current main (v0.7.133); PR #295 fixed the same class of bug for bundle/cli.js (splitting) but not for the hook.

Fix

  • src/hooks/graph-on-stop.ts — load runBuildCommand lazily via await import("../commands/graph.js") behind the gate, inside the existing try/catch. A missing extractor degrades to a logged skip + exit 0.
  • esbuild.config.mjs — build graph-on-stop for the four harnesses via buildGraphOnStop() with splitting: true (mirrors the CLI fix), so the dynamic import stays a runtime chunk load and tree-sitter lives only in graph-chunks/. Clears stale hashed chunks first; removed graph-on-stop from the four shared harness build lists.
  • esbuild.config.mjs — the lazy import() defeats tree-shaking in the non-split openclaw graph-worker build, pulling user-config.ts's process.env.HIVEMIND_CONFIG_PATH into openclaw/dist/graph-on-stop.js and tripping ClawHub's env-harvesting critical. Rewrote HIVEMIND_CONFIG_PATHundefined in the existing openclawGraphWorkerDefine (call site has a ?? homedir()/.deeplake/config.json fallback). The openclaw graph-worker build is otherwise untouched.
  • tests — a unit reject-path test + a bundle-level guard asserting each of the four shipped entries has no static tree-sitter import and loads it only via graph-chunks/.

Scope

Fixes the four harness bundles that register graph-on-stop as a Stop/SessionEnd hook (the visible exit-1 surface). OpenClaw's graph-on-stop/graph-pull-worker and graph-pull-worker on the other harnesses keep the same latent tree-sitter gap but run detached, so they can't break the session lifecycle — left as follow-up.

Verification

pre-fix (current main) fixed
tree-sitter absent exit 1ERR_MODULE_NOT_FOUND exit 0 — logs build threw: Cannot find package 'tree-sitter'
tree-sitter present builds graph builds graph — no regression
  • Bug reproduced on a clean current-main build (git worktree): codex graph-on-stop.js had 3 top-level tree-sitter imports.
  • Fixed bundle: 4 entries with 0 static tree-sitter imports; e2e run exits 0.
  • ClawHub audit: 0 critical.
  • Full suite: 5541 passed.
  • Codex review of the diff: "approve; no blocking correctness issues."

Session Context

Session transcript

Summary by CodeRabbit

  • Bug Fixes

    • Improved graph updates triggered by stop hooks when optional native components are unavailable.
    • Prevented missing components from causing the hook process to fail unexpectedly.
    • Ensured build locks are released and clear error details are recorded when graph generation fails.
    • Improved harness bundle loading by deferring graph-related components until they are needed.
  • Tests

    • Added coverage validating reliable graph hook behavior across supported harnesses.

The graph-on-stop Stop/SessionEnd hook (claude-code, codex, cursor, hermes)
statically imported the tree-sitter extractor chain (runBuildCommand ->
extract/index -> import Parser from "tree-sitter"). esbuild hoisted the
external tree-sitter import to the top of the bundle, so Node resolved it at
module load -- before main()'s try/catch -- and on an install where the
optional native grammar failed to build (Node 24 / arm64) the hook aborted
with ERR_MODULE_NOT_FOUND and exit code 1. Mirrors the CLI fix already in
bundle/cli.js (splitting), which only covered the CLI, not the hook.

- src/hooks/graph-on-stop.ts: load runBuildCommand lazily via
  `await import("../commands/graph.js")` behind the gate, inside the existing
  try/catch. A missing extractor now degrades to a logged skip + exit 0.
- esbuild.config.mjs: build graph-on-stop for the four harnesses via
  buildGraphOnStop() with splitting, so the dynamic import stays a runtime
  chunk load and tree-sitter lives only in graph-chunks/; clears stale hashed
  chunks first; removed graph-on-stop from the four shared harness lists.
- esbuild.config.mjs: the lazy import() defeats tree-shaking in the non-split
  openclaw graph-worker build, pulling user-config.ts's HIVEMIND_CONFIG_PATH
  into openclaw/dist/graph-on-stop.js and tripping ClawHub's env-harvesting
  critical. Rewrite HIVEMIND_CONFIG_PATH to undefined in the existing
  openclawGraphWorkerDefine (the call site has a ?? homedir() fallback).
- tests: unit reject-path coverage + a bundle-level guard asserting each of
  the four shipped entries has no static tree-sitter import and loads it only
  via graph-chunks/.

Verified: bug reproduced on a clean current-main build (worktree); the fixed
bundle exits 0 (graceful) with tree-sitter absent and still builds the graph
when present; ClawHub audit 0 critical; full suite 5541 passed.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Graph-on-stop is built separately for each harness with tree-sitter isolated into lazy chunks. The hook dynamically loads the graph command after gate and lock checks, while regression tests cover bundle structure and missing-native-dependency handling.

Changes

Graph-on-stop tree-sitter isolation

Layer / File(s) Summary
Per-harness graph-on-stop bundles
esbuild.config.mjs
Builds standalone code-split graph-on-stop bundles with external tree-sitter modules, deterministic chunks, executable entries, and updated harness wiring.
Deferred graph command loading
src/hooks/graph-on-stop.ts
Resolves runBuildCommand after gate and lock checks through an injected dependency or dynamic import inside existing error handling.
Bundle and failure-path regression coverage
tests/shared/graph/graph-on-stop-bundle.test.ts, tests/shared/graph/graph-on-stop-main.test.ts
Verifies lazy tree-sitter chunk placement and handling of missing tree-sitter during graph execution.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: thenotoriousllama

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: preventing graph-on-stop crashes when tree-sitter is missing.
Description check ✅ Passed The description covers the problem, fix, scope, and verification, but it doesn't use the template's explicit Version Bump and Test plan sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/graph-on-stop-lazy-treesitter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 90.48% (🎯 90%) 57 / 63
🔴 Statements 89.55% (🎯 90%) 60 / 67
🔴 Functions 87.50% (🎯 90%) 7 / 8
🔴 Branches 71.11% (🎯 90%) 32 / 45
File Coverage — 1 file changed
File Stmts Branches Functions Lines
src/hooks/graph-on-stop.ts 🔴 89.5% 🔴 71.1% 🔴 87.5% 🟢 90.5%

Generated for commit da981eb.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/shared/graph/graph-on-stop-bundle.test.ts`:
- Around line 47-50: Update the static-import assertion in the “entry does NOT
statically import tree-sitter” test to match both from-clause imports and
side-effect imports such as import "tree-sitter". Apply the same regex
correction to the related assertion around the additional referenced lines,
preserving the existing load-time regression coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d1058e73-979d-4b8b-bc00-754ac662873c

📥 Commits

Reviewing files that changed from the base of the PR and between c4f1ef6 and 398d395.

📒 Files selected for processing (4)
  • esbuild.config.mjs
  • src/hooks/graph-on-stop.ts
  • tests/shared/graph/graph-on-stop-bundle.test.ts
  • tests/shared/graph/graph-on-stop-main.test.ts

Comment on lines +47 to +50
it("entry does NOT statically import tree-sitter", () => {
// Any `from "tree-sitter..."` in the entry means the native import was
// hoisted to module top → the exact load-time crash we fixed.
expect(entrySrc).not.toMatch(/from\s*["']tree-sitter/);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Detect side-effect static imports too.

The regex misses import "tree-sitter" syntax, allowing the original module-load failure to regress while this test passes.

Proposed fix
-        expect(entrySrc).not.toMatch(/from\s*["']tree-sitter/);
+        expect(entrySrc).not.toMatch(
+          /(?:\bfrom\s*|^\s*import\s*)["']tree-sitter[^"']*["']/m,
+        );
...
-          /from\s*["']tree-sitter/.test(readFileSync(join(b.chunkDir, f), "utf8")),
+          /(?:\bfrom\s*|^\s*import\s*)["']tree-sitter[^"']*["']/m.test(
+            readFileSync(join(b.chunkDir, f), "utf8"),
+          ),

Also applies to: 57-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/shared/graph/graph-on-stop-bundle.test.ts` around lines 47 - 50, Update
the static-import assertion in the “entry does NOT statically import
tree-sitter” test to match both from-clause imports and side-effect imports such
as import "tree-sitter". Apply the same regex correction to the related
assertion around the additional referenced lines, preserving the existing
load-time regression coverage.

@efenocchi
efenocchi merged commit 254f498 into main Jul 18, 2026
11 checks passed
@efenocchi
efenocchi deleted the fix/graph-on-stop-lazy-treesitter branch July 18, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant