Howto: Learning Capture for Claude Code

What it does

synonyms:: howto, learning capture claude code, howto learning capture for claude code checklist:: what it does, architecture, prerequisites, setup, how learning capture works, hook script details, key differences from opencode hooks, troubleshooting

Claude Code automatically captures failed bash commands and stores them as learnings. When you run the same failing command again, the pre-tool-use hook warns you. Both Claude Code and opencode share the same learning store.

Architecture

Claude Code hooks (~/.claude/hooks/)
    |
    |--- PreToolUse  (pre_tool_use.sh)
    |       |---> terraphim-agent guard --json                   (safety)
    |       |---> terraphim-agent replace --role ... --json      (knowledge graph rewrites)
    |       |---> terraphim-agent hook --hook-type pre-tool-use  (generic pre handler)
    |
    |--- PostToolUse (post_tool_use.sh)
    |       |---> terraphim-agent learn hook --format claude     (capture failed commands)
    |       |---> terraphim-agent hook --hook-type post-tool-use (generic post handler)
    |
                                              |
                                              v
                                     Shared learning store
                                  (~/.config/terraphim/learnings/)

Prerequisites

  • terraphim-agent installed at ~/.cargo/bin/terraphim-agent
  • Claude Code v2.1+ installed (npm install -g @anthropic-ai/claude-code)

Setup

1. Hook scripts (already installed)

The hook scripts live at:

  • ~/.claude/hooks/pre_tool_use.sh — runs before every Bash tool call
  • ~/.claude/hooks/post_tool_use.sh — runs after every Bash tool call

2. Hook registration (already configured)

Hooks can be registered in either ~/.claude/settings.json (shared, version-controlled) or ~/.claude/settings.local.json (personal, git-ignored). This setup uses settings.local.json so per-developer paths do not leak into the repo. On teams that want everyone to share the same hooks, move the block into settings.json.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre_tool_use.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/post_tool_use.sh"
          }
        ]
      }
    ]
  }
}

3. Verify it works

# Simulate what Claude Code sends when a command fails
echo '{"tool_name":"Bash","tool_input":{"command":"nonexistent-test-cmd"},"tool_result":{"exit_code":127,"stdout":"","stderr":"command not found"}}' \
  | ~/.claude/hooks/post_tool_use.sh

# Check the learning was captured
terraphim-agent learn query "nonexistent-test-cmd"

Expected output:

Learnings matching 'nonexistent-test-cmd'.
  [G] [cmd] nonexistent-test-cmd (exit: 127)

How learning capture works

Capture (automatic)

When Claude Code runs a Bash command that fails, Claude Code sends JSON to the PostToolUse hook via stdin:

{
  "tool_name": "Bash",
  "tool_input": { "command": "the-failed-command" },
  "tool_result": { "exit_code": 1, "stdout": "...", "stderr": "error output" }
}

The post_tool_use.sh script:

  1. Locates terraphim-agent binary (4 fallback paths)
  2. Reads JSON from stdin
  3. Pipes to terraphim-agent learn hook --format claude
  4. Also runs terraphim-agent hook --hook-type post-tool-use --json
  5. Always fails open (|| true) — never blocks Claude Code

Pre-tool-use warnings (automatic)

Before each Bash command, pre_tool_use.sh runs terraphim-agent hook --hook-type pre-tool-use --json which checks the command against past learnings and the role-configured knowledge graph. If it matches a past failure or a recorded correction, the hint is surfaced to the LLM via the hook's structured JSON response — never directly to the user terminal.

Claude Code and opencode now align on the same UX principle: pre-tool-use hints are for LLM consumption only, keeping the user interface clean while still informing the model about past failures and corrections.

Safety guard (automatic)

pre_tool_use.sh also runs terraphim-agent guard which blocks destructive commands like git reset --hard, rm -rf, etc. If blocked, the hook returns:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "BLOCKED: <reason>"
  }
}

Correction (manual)

Add corrections to teach the system what to do instead:

# Record that 'npm install' should be 'bun install'
terraphim-agent learn correction \
  --original "npm install" \
  --corrected "bun install" \
  --correction-type "tool-preference"

# Record that a specific command has a known fix
terraphim-agent learn correction \
  --original "cargo test --all" \
  --corrected "cargo test --workspace" \
  --correction-type "tool-preference"

# Capture a user correction inline (used in conversation)
terraphim-agent learn correction \
  --original "pip install" \
  --corrected "uv pip install" \
  --correction-type "tool-preference"

Query (manual)

Search past learnings:

# Search by keyword
terraphim-agent learn query "docker"

# Search exactly
terraphim-agent learn query "npm install" --exact

# List recent learnings
terraphim-agent learn list --recent 10

# Query and see corrections
terraphim-agent learn query "npm"

Hook script details

post_tool_use.sh

stdin -> JSON { tool_name, tool_input, tool_result }
         |
         v
    Find terraphim-agent binary
    (~/.cargo/bin/ -> target/release/ -> target/debug/ -> PATH)
         |
         v
    cd ~/.config/terraphim  (for KG access)
         |
         v
    terraphim-agent learn hook --format claude  (capture failed commands)
         |
         v
    terraphim-agent hook --hook-type post-tool-use --json  (generic handler)
         |
         v
    stdout passthrough (always succeeds, || true)

pre_tool_use.sh

stdin -> JSON { tool_name, tool_input }
         |
         v
    Filter: only Bash commands
         |
         v
    Step 1: terraphim-agent guard --json  (block destructive commands)
         |   If blocked -> return deny JSON
         v
    Step 2: terraphim-agent replace --role "Terraphim Engineer" --json
         |   (knowledge graph text replacement for git commits, package managers)
         v
    Step 3: terraphim-agent hook --hook-type pre-tool-use --json
         (generic pre-tool handler)

Key differences from opencode hooks

| Aspect | Claude Code | opencode | |--------|-------------|----------| | Hook type | Shell scripts (bash) | JavaScript plugins (Bun) | | Registration | settings.local.json | Auto-loaded from plugin/ dir | | Input format | JSON on stdin | JS objects (input, output) | | Tool name | "Bash" (capital B) | "bash" (lowercase) | | Exit code | Structured { exit_code: N } | Raw string (parse from output) | | Blocking | Return permissionDecision: "deny" | throw new Error() | | Fail-open | \|\| true | catch {} |

Troubleshooting

Learnings not being captured

  1. Check terraphim-agent is installed:
~/.cargo/bin/terraphim-agent learn list --recent 5
  1. Test the hook directly:
echo '{"tool_name":"Bash","tool_input":{"command":"test-verify-hook"},"tool_result":{"exit_code":1,"stdout":"","stderr":"error"}}' \
  | ~/.claude/hooks/post_tool_use.sh
terraphim-agent learn query "test-verify-hook"
  1. Check hook is registered:
cat ~/.claude/settings.local.json | grep -A5 "PostToolUse"

Hook script not executable

chmod +x ~/.claude/hooks/pre_tool_use.sh ~/.claude/hooks/post_tool_use.sh

Safety guard blocking legitimate commands

The safety guard uses terraphim-agent guard which checks for destructive patterns. If a legitimate command is blocked, the guard can be bypassed by running the command outside Claude Code (directly in terminal).

See also