Skip to content

Grok Build CLI support: search + skills already work via Claude-plugin compat; hooks don't load #301

Description

@HF-teamdev

Update (2026-09-19): a working Grok bridge, tested live, is in this comment. It includes the full source, the real Grok payload keys, and a hook log. Fix checklist:

  • Installer: write ~/.grok/hooks/ruvnet-brain.json (always trusted) that points at a stable grok-hook.mjs, the same way Codex uses codex-hook.mjs. Detect Grok with ~/.grok/bin/grok.
  • In the Grok wrapper (or hook-shim.mjs), add snake_case aliases for Grok's camelCase keys. This is critical for the stop_hook_active loop guard (ADR-0043).
  • Map Grok tool names to Claude's (search_replace → Edit/Write, run_terminal_command → Bash, …).
  • Skip the Stop gates when reason != "end_turn".
  • Register SessionStart for Grok without a matcher. startup|resume|… didn't fire; confirmed fixed live in Update 2.
  • Make --doctor and the installer report Grok's state instead of "not wired". Don't add a second MCP entry to ~/.grok/config.toml.
  • Offer to add the "call search_ruvnet first" line to ~/.grok/AGENTS.md. Grok discards UserPromptSubmit and SessionStart context, and live testing showed this line is what makes Grok ground itself (Update 2).
  • Separate issue: a model download that doesn't finish leaves search broken with a misleading "sandbox" error. See the section at the bottom.

Summary

Grok Build CLI (xAI's grok, v1.0.34) already picks up the RuvNet Brain Claude Code plugin, because it reads ~/.claude/plugins/installed_plugins.json. I tested it live:

  • ✅ search_ruvnet works in Grok. It's called as ruvnet-brain__search_ruvnet and returns real cited source.
  • ✅ All 9 skills load (/rvbc, /brain-build, /brain-score, …).
  • ❌ None of the lifecycle hooks run. grok inspect finds hooks/hooks.json, but /hooks-list in a live session doesn't show any RuvNet hooks.
  • ⚠️ Even once they load, two contract gaps would bite. Grok drops additionalContext from UserPromptSubmit and SessionStart, and it sends hook input with camelCase keys.

So Grok users get the tool and the skills, but not the proactive grounding. A small grok host adapter would probably close most of the gap. Details below.

Environment

  • macOS (Darwin 27), Node on PATH
  • ruvnet-brain 4.3.26, installed with npx ruvnet-brain for Claude Code and Codex. The installer printed "Grok was not wired".
  • Grok Build grok 1.0.34 (3736acbc8658) [stable]

Live test results in Grok

1. MCP search: works

Prompt: Call the ruvnet-brain MCP tool search_ruvnet with query "RuVector RVF file format" and k=3…

Tool name: ruvnet-brain__search_ruvnet
Succeeded: yes
Source paths:
  1. ruvector/crates/rvf/rvf-wire/Cargo.toml
  2. ruvector/npm/packages/rvf/src/index.ts
  3. ruvector/crates/rvf/rvf-manifest/Cargo.toml

2. Hooks: discovered but not loaded

grok inspect --json lists the plugin with "hooks": true and a hook entry pointing at …/ruvnet-brain/4.3.26/hooks/hooks.json. But /hooks-list in the same session shows only my own 4 statusline hooks:

Loaded hooks (4):
  global/statusline-compact:pre_compact[0] ...  ~/.grok/statusline/compact-hook.zsh
  global/settings:pre_compact[0] ...            ~/.claude/statusline/compact-hook.zsh
  global/statusline-compact:post_compact[0] ... ~/.grok/statusline/compact-hook.zsh
  global/settings:post_compact[0] ...           ~/.claude/statusline/compact-hook.zsh

Grok does load hooks from ~/.claude/settings.json (the global/settings rows), and [compat.claude] hooks is on. So Claude hook compat isn't disabled. It's specifically the plugin's hooks.json that isn't being activated.

Likely cause: grok plugin list prints "No plugins installed". Grok only discovered RuvNet as a Claude-compat plugin; it isn't a native Grok install. Grok's plugin docs say "Enabled plugins require trust to load skills, commands, hooks, MCP servers", and only plugins under ~/.grok/plugins/ are trusted automatically. In practice the compat plugin's skills and MCP load but its hooks don't. grok plugin validate <plugin dir> reports "Plugin manifest is valid", so the manifest isn't the problem.

What works around it: hooks placed in ~/.grok/hooks/*.json are always trusted. So the installer could write a ~/.grok/hooks/ruvnet-brain.json that points at a stable grok-hook.mjs wrapper, the same way Codex uses ~/.cache/ruvnet-brain/codex-hook.mjs. That wrapper is also the natural place to handle the key-name differences below.

Hook contract differences to plan for (from ~/.grok/docs/user-guide/10-hooks.md)

  1. UserPromptSubmit can't inject context. The docs say: "an allowing hook's stdout / additionalContext is discarded rather than added as context." So ground-ruvnet's "ground before you assert" directive never reaches Grok's model. It can still block, with exit 2 or {"decision":"block"}.

  2. SessionStart is passive. Its output doesn't reach the model, so the "RuvNet Brain vX active / grounding PROVEN" banner is lost.

  3. Hook input uses camelCase keys. Grok sends toolName, toolInput, stopHookActive, lastAssistantMessage. Only hook_event_name gets a snake_case copy. These scripts read the Claude spelling:

    • scripts/decision-gate.mjs:222-223 reads tool_name / tool_input
    • scripts/continuation-gate.mjs:265 reads stop_hook_active
    • scripts/grounding-turn-gate.mjs:116 reads stop_hook_active

    The stop_hook_active ones matter most. Under Grok, that loop guard would never trip, and only Grok's built-in cap of 8 continuations per turn would stop the gate. Normalizing keys in hook-shim.mjs (camelCase → snake_case) would fix all three in one place.

  4. Extra Stop events at session end. Grok fires Stop again with reason: "channel_closed" or "shutdown". Counters and gates should act only when reason == "end_turn".

  5. What already matches: Stop decision:block / additionalContext (keeps the agent working), PreToolUse permissionDecision / deny, and PostToolUse additionalContext. CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA / CLAUDE_PROJECT_DIR are all set. Grok's MCP tool names (server__tool) already match your ^(?:.*__)?search_ruvnet$ matcher, and Grok maps Claude tool names in matchers (e.g. Edit also matches search_replace).

Suggested changes

  1. Add a host-adapters/grok.json that detects ~/.grok/bin/grok, so the installer and --doctor report "Grok: search + skills via Claude-plugin compat; hooks " instead of "not wired". It shouldn't add a second [mcp_servers.ruvnet-brain] to ~/.grok/config.toml, since Grok already loads the plugin's .mcp.json.
  2. Host detection. session-start-core.mjs:136 branches on RUVNET_HOOK_HOST === 'codex'. Grok sets GROK_AGENT (plus GROK_PLUGIN_ROOT and GROK_WORKSPACE_ROOT) in the hook environment, so the shim could set RUVNET_HOOK_HOST=grok from those.
  3. Normalize hook input keys in hook-shim.mjs, per item 3 above.
  4. Grounding fallback for Grok. Since prompt-time injection isn't possible, lean on the Stop grounding-turn-gate, which Grok honors, and on the ruvnet-brain skill body. Or suggest adding a one-line rule to ~/.grok/AGENTS.md.
  5. Docs. explainer/llms-full.txt lists Claude Code and Codex as the hosts. A line on Grok support and its limits would help.

Unrelated, but hit on the same machine: embedder download

On first install, the ONNX weights for Xenova/bge-base-en-v1.5 and the ms-marco-MiniLM-L-6-v2 reranker (onnx/model_quantized.onnx) never finished downloading. Only the tokenizer and config files landed. On this connection, the huggingface.co redirect alone took about 3.7s, close to the worker's 5s reachability timeout. search_ruvnet then failed everywhere with "cannot reach the embedding-model host … This environment looks network-restricted (sandbox/container?)", which was misleading because the network was fine.

Downloading the two files by hand into the pinned revision folders fixed it (105 MB + 22 MB, about 2 minutes on the same network). --doctor then said "Grounding PROVEN" in 16.8s. A longer timeout or a resumable download at install time would avoid this. --doctor still shows "Codex MCP is registered but degraded" with the old warmup error, which looks like a cached status from before the fix.

Not verified

  • Why the plugin's hooks don't activate in Grok (trust, a compat gap, or a Grok bug).
  • Whether Grok also sends snake_case copies of tool_name and the other keys. The docs only mention hook_event_name.
  • Whether decision-gate's matcher catches Grok's file-write tools.

Happy to test a branch on Grok if that helps. Thanks for building this, Stu!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions