Skip to content

Grok Claude-hook fast path does not count a use_tool recall as a consult #472

Closed

Description

@CryptoJones

What happened

Grok Build on this machine runs the Claude PreToolUse hook (omi-guard.sh), not only omind guard adapter --harness grok. v10.3.0 translates camelCase Grok payloads in Python. The bash fast path still reads .tool_name only.

A memory recall arrives as use_tool with the real tool nested at tool_input.tool_name (omi__recall-note) and the arguments at tool_input.tool_input. The script does not unwrap that, so the call is not mcp__omi__* and is not recorded as a consult. The repo-work gate then denies the recall that would have cleared it.

The same gap hits a snake_case-only event (tool_name: use_tool or run_terminal_command, no toolName). payload_is_grok is false for that shape, so translate_event("claude", event) returns it unchanged and PostToolUse does not record the consult either. run_terminal_command also skips the Bash hard rules on the fast path.

Expected

  • use_tool / CallMcpTool whose inner tool is omi__recall-note (snake_case or camelCase) is a consult of the inner note name. list-notes still does not clear the gate. create-note / edit-note / delete-note / restore-note still are not consults.
  • run_terminal_command is judged as Bash.
  • A plain Claude event is unchanged.

Activity

  1. CryptoJones commented on Oct 9, 2026

    @CryptoJones
    OwnerAuthor

    🏭 Factory groom — groomed. An agent can pick this up as written. Edit the description or reply here and it gets reviewed again on a later tick.

    I'd mark this one groomed: the bug is real, it's confined to one subsystem (the guard), and one PR can fix it. One part is worse than the issue says. I checked all of this by reading the code at 387873f (v10.3.0). I didn't run anything.

    What the code shows

    • The shell fast path only knows the Claude field names. src/omind/omi-guard.sh reads the tool with jq -r '.tool_name // empty' and never unwraps use_tool or CallMcpTool. The Python translation from feat(grok): support the Grok Build client #471 (harness._translate_grok, harness.py:399-450) only runs when jq is missing, when the shell hands off to guard adapter, or in the PostToolUse hooks.
      • A snake_case use_tool recall reaches the generic guard check as tool use_tool. That is not a consult, so the gate denies it.
      • A camelCase event has no .tool_name at all, so tool comes out empty and gets the same treatment.
    • The camelCase case also lets destructive shell commands through. Take a camelCase {"toolName":"run_terminal_command","toolInput":{"command":"gh repo delete …"}}. It never reaches the [ "$tool" = "Bash" ] branch. It drops to the generic delegation with command:"", so the guard's hard blocks on dangerous commands (the seed rules, like "never delete a repo") are never checked. tests/test_grok_client.py::test_claude_hook_denies_grok_payload_as_json only covers the Python path, so it doesn't catch this. Fail-open must never skip a hard block, which makes this the most important part of the fix.
    • Snake_case-only Grok events miss the translation in Python too. payload_is_grok (harness.py:385) only looks for hookEventName, toolName or toolInput. So translate_event("claude", …) passes the event through unchanged. PostToolUse (hooks.py) then never lifts the inner omi__recall-note, and verify.py's consult extraction never sees it.
    • The Python adapter counts some tools as consults that the shell refuses. adapters.normalize_action (adapters.py:105) treats every mcp__omi__* call as a consult, including list-notes and create-note. omi-guard.sh and verify.py:370-388 both refuse those (verifier: credit guard-demanded note reads; stop scoring vault writes as consults; log judgement context on off-topic denials #148, and the anti-dodge rule from Consult-gate dodge: reading the vault index (index.md) clears the gate without a real consult #109). So simply routing Grok payloads from the shell to omind guard adapter would bring back the gate-dodge the issue says must not come back.

    What one PR should do

    1. Make omi-guard.sh read the Grok shapes:
      • toolName, sessionId and toolInput as fallbacks for the snake_case fields;
      • the use_tool / CallMcpTool wrapper, unwrapped to the inner tool and its arguments;
      • omi__x, omi_x and bare tool names mapped to mcp__omi__x;
      • run_terminal_command treated as Bash.
    2. Make translate_event handle snake_case-only use_tool, CallMcpTool and run_terminal_command events.
    3. Add regression tests for each case in the issue's "Expected" list.

    There's nothing for a human to decide here: no note format, MCP schema or CLI surface changes.

    Factory grooming notes

    Scope: guard subsystem only — src/omind/omi-guard.sh, src/omind/harness.py (payload_is_grok / _translate_grok), and src/omind/adapters.py only if you delegate (see below). No new subcommand, hook, or harness.

    1. omi-guard.sh fast path (the main bug):

      • Read the tool as .tool_name // .toolName, the input as .tool_input // .toolInput, the session as .session_id // .sessionId, the transcript as .transcript_path // .transcriptPath.
      • If the tool is use_tool or CallMcpTool, lift the inner name (tool_name // toolName // name) and the inner args (tool_input // arguments // toolInput). Then map names the way _translate_grok does: omi__x -> mcp__omi__x, omi_x -> mcp__omi__x, bare recall-note/search-vault/... -> mcp__omi__x.
      • Map run_terminal_command -> Bash BEFORE the Bash branch, so the hard rules run and fail closed exactly as for Claude's Bash. Today a camelCase run_terminal_command reaches the generic guard check with command:"" and skips the hard rules. Add a test where gh repo delete sent this way is blocked (exit 2).
      • Keep the existing exclusions: list-notes/list-tags/graph-*/backlinks are allowed but do not clear the gate; create/edit/delete/restore are ordinary gated actions. Consult targets must come from the INNER args.
      • Alternative: hand Grok-shaped payloads to omind guard adapter --harness claude, which already translates and renders FMT_GROK. If you do, first make adapters.normalize_action stop marking list-notes/list-tags/graph-*/backlinks and create/edit/delete/restore-note as consults (today it marks every mcp__omi__*, adapters.py:105). Otherwise that path re-opens verifier: credit guard-demanded note reads; stop scoring vault writes as consults; log judgement context on off-topic denials #148/Consult-gate dodge: reading the vault index (index.md) clears the gate without a real consult #109. Keep the shell's fail-closed handling for any rc other than 0/2 on shell commands.
    2. harness.py: make a snake_case-only event with tool_name in _GROK_DISPATCHERS or == "run_terminal_command" go through _translate_grok. Do NOT widen detection to the whole _GROK_TOOL_ALIASES table: read_file (and other lowercase names) overlap Gemini's tool names, and gemini events also pass through payload_is_grok. Keep test_claude_event_is_not_treated_as_grok green and add a plain-Claude-unchanged case.

    3. Tests (CI runs on real Windows):

      • Translation tests in tests/test_grok_client.py run everywhere.
      • Shell-hook tests use the rendered-hook helper in tests/test_guard.py (~line 976) and carry the existing _HOOK_TESTABLE skip.
      • Cover: snake_case and camelCase use_tool -> omi__recall-note clears the gate (a following Edit is allowed); list-notes via use_tool does not; create-note via use_tool is not a consult; run_terminal_command destructive command blocked; plain Claude event unchanged.
      • PostToolUse: a snake_case use_tool recall is recorded as a consult by verify.
      • Live check: pipe each payload through a rendered omi-guard.sh with a temp state dir and confirm exit codes and the gate sentinel.
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

    factory:groomedVetted by the factory; eligible for an agent to implement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions