Skip to content

Commit d706c33

Browse files
authored
Merge pull request #1337 from abendrothj/bash-tool-hook-augment
feat(hook-augment): augment Bash tool searches via graph
2 parents 4b3ab38 + 09c6624 commit d706c33

7 files changed

Lines changed: 416 additions & 18 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ overwrite user-modified agents.
482482

483483
| Agent | Activation | MCP config | Durable context / augmentation |
484484
|-------|------------|------------|--------------------------------|
485-
| Claude Code | Detected | `~/.claude.json` | Skill + three exact-tool graph agents; `SessionStart`, `SubagentStart`, non-blocking `PreToolUse` for `Grep`/`Glob`, and post-`Read` coverage |
485+
| Claude Code | Detected | `~/.claude.json` | Skill + three exact-tool graph agents; `SessionStart`, `SubagentStart`, non-blocking `PreToolUse` for `Grep`/`Glob`/`Bash`, and post-`Read` coverage |
486486
| Codex CLI | Detected | `$CODEX_HOME/config.toml` | `AGENTS.md`, skill, three read-only agents; `SessionStart` + `SubagentStart` |
487487
| Gemini CLI | Detected | `.gemini/settings.json` | `GEMINI.md`, three explicit read/graph-tool subagents; `BeforeTool`, `AfterTool` `read_file` coverage, and `SessionStart` |
488488
| Zed | Detected | platform `settings.json` (JSONC) | `AGENTS.md` + shared skill |
@@ -531,7 +531,7 @@ overwrite user-modified agents.
531531
### Sessions, compaction, and subagents
532532

533533
Hooks installed by this project are fail-open and context-only. Claude Code's
534-
`PreToolUse` observes `Grep`/`Glob` and injects matching graph symbols as
534+
`PreToolUse` observes `Grep`/`Glob`/`Bash` and injects matching graph symbols as
535535
`additionalContext`; `PostToolUse` on `Read` adds targeted coverage context when
536536
the graph could not fully parse or index that file. It never denies or replaces
537537
the requested tool call.

‎scripts/smoke-test.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,23 +1636,23 @@ fi
16361636
echo "OK 8c-i: Claude exact-tool graph subagent"
16371637

16381638
# 8d: Claude Code hooks keep search augmentation and read-coverage reporting
1639-
# separate: PreToolUse matches exactly Grep|Glob, while PostToolUse matches
1639+
# separate: PreToolUse matches exactly Grep|Glob|Bash, while PostToolUse matches
16401640
# exactly Read. Neither hook may grow a Search or catch-all matcher.
16411641
if ! cat "$FAKE_HOME/.claude/settings.json" 2>/dev/null | python3 -c "
16421642
import json, sys
16431643
d = json.load(sys.stdin)
16441644
all_hooks = d.get('hooks', {})
16451645
pre = all_hooks.get('PreToolUse', [])
16461646
post = all_hooks.get('PostToolUse', [])
1647-
ok = (any(h.get('matcher') == 'Grep|Glob' for h in pre) and
1647+
ok = (any(h.get('matcher') == 'Grep|Glob|Bash' for h in pre) and
16481648
any(h.get('matcher') == 'Read' for h in post))
16491649
bad = any('Search' in str(h.get('matcher', '')) for h in pre + post)
16501650
sys.exit(0 if (ok and not bad) else 1)
16511651
" 2>/dev/null; then
16521652
echo "FAIL 8d: Claude search/read hook matchers are not exact"
16531653
exit 1
16541654
fi
1655-
echo "OK 8d: Claude Code PreToolUse Grep|Glob + PostToolUse Read"
1655+
echo "OK 8d: Claude Code PreToolUse Grep|Glob|Bash + PostToolUse Read"
16561656

16571657
# 8e: Claude Code shim script — must be non-blocking augmenter, not a gate.
16581658
# #929: Windows installs a .cmd script (extensionless bash shims triggered the

‎src/cli/cli.c‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4141,9 +4141,9 @@ static int cbm_remove_grok_mcp_owned(const char *binary_path, const char *config
41414141

41424142
/* ── Claude Code pre-tool hooks ───────────────────────────────── */
41434143

4144-
/* Search augmentation runs before Grep/Glob; exact coverage context runs after
4144+
/* Search augmentation runs before Grep/Glob/Bash; exact coverage context runs after
41454145
* Read. Both adapters are context-only and fail open. */
4146-
#define CMM_HOOK_SEARCH_MATCHER "Grep|Glob"
4146+
#define CMM_HOOK_SEARCH_MATCHER "Grep|Glob|Bash"
41474147
#define CMM_HOOK_READ_MATCHER "Read"
41484148
/* Basename only; the full command path is resolved at install time via
41494149
* cbm_resolve_hook_command so $CLAUDE_CONFIG_DIR is honored. */
@@ -4167,6 +4167,7 @@ static int cbm_remove_grok_mcp_owned(const char *binary_path, const char *config
41674167
static const char *const cmm_claude_old_matchers[] = {
41684168
"Grep|Glob|Read|Search",
41694169
"Grep|Glob|Read",
4170+
"Grep|Glob",
41704171
NULL,
41714172
};
41724173
static const char *const cmm_gemini_old_matchers[] = {
@@ -5283,7 +5284,7 @@ static int cbm_remove_owned_hook_script(const char *path, const char *expected_c
52835284

52845285
/* Install the search-augmenter shim to ~/.claude/hooks/.
52855286
* The shim is a thin wrapper that delegates to `<binary> hook-augment`,
5286-
* which adds graph context to Grep/Glob calls. It NEVER blocks a tool call:
5287+
* which adds graph context to Grep/Glob/Bash search calls. It NEVER blocks a tool call:
52875288
* a missing/old/hung binary results in a silent exit 0 (issue #362/#288).
52885289
* The legacy filename `cbm-code-discovery-gate` is retained so existing
52895290
* settings.json entries and uninstall keep working with zero migration. */
@@ -7783,7 +7784,7 @@ static void install_claude_code_config(const char *home, const char *binary_path
77837784
}
77847785
}
77857786
if (gate_ok) {
7786-
printf(" hooks: PreToolUse Grep/Glob search augmentation + PostToolUse Read coverage "
7787+
printf(" hooks: PreToolUse Grep/Glob/Bash search augmentation + PostToolUse Read coverage "
77877788
"(non-blocking)\n");
77887789
}
77897790
if (session_ok) {

‎src/cli/cli.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ bool cbm_hook_augment_invocation_supported_for_testing(const char *dialect,
248248
bool cbm_hook_path_contains_for_testing(const char *root, const char *candidate,
249249
bool case_insensitive);
250250
const char *cbm_hook_no_project_index_guidance_for_testing(const char *event);
251+
bool cbm_hook_augment_parse_bash_pattern_for_testing(const char *cmd, char *out, size_t out_sz);
251252
bool cbm_mcp_command_path_probe_safe_for_testing(const char *command, bool windows);
252253
void cbm_set_mcp_command_path_probe_counter_for_testing(int *counter);
253254
int cbm_install_editor_mcp_with_previous_for_testing(const char *binary_path,
@@ -310,7 +311,7 @@ const char *cbm_get_aider_instructions(void);
310311
/* ── Pre-tool hook management ─────────────────────────────────── */
311312

312313
/* Upsert a PreToolUse hook in ~/.claude/settings.json for Claude Code.
313-
* Adds a Grep|Glob matcher that reminds to use MCP tools.
314+
* Adds a Grep|Glob|Bash matcher that reminds to use MCP tools.
314315
* Returns 0 on success. */
315316
int cbm_upsert_claude_hooks(const char *settings_path);
316317

@@ -506,7 +507,7 @@ int cbm_cmd_config(int argc, char **argv);
506507

507508
/* hook-augment: stdin-driven Claude Code PreToolUse augmenter.
508509
* Reads the hook JSON from stdin and emits hookSpecificOutput.additionalContext
509-
* with search_graph hits for Grep/Glob calls. NEVER blocks: every failure
510+
* with search_graph hits for Grep/Glob/Bash search calls. NEVER blocks: every failure
510511
* path returns 0 with no stdout output. */
511512
int cbm_cmd_hook_augment(int argc, char **argv);
512513

@@ -524,6 +525,10 @@ char *cbm_hook_augment_lifecycle_json_for(const char *input, const char *forced_
524525
* hard fail-open deadline without constructing a local MCP/store instance. */
525526
void cbm_hook_augment_arm_deadline(void);
526527
char *cbm_hook_augment_read_stdin(void);
528+
/* Pure no-op gate for the hook-client fast path (see hook_augment.c). */
529+
bool cbm_hook_augment_input_is_noop_bash(const char *input);
530+
/* Hand back stdin bytes main() consumed early; the next read returns them. */
531+
void cbm_hook_augment_prefetch_stdin(char *owned);
527532

528533
/* Why a hook client is not augmenting. The hook caller only ever sees stdout,
529534
* so each reason that is actionable by the user must have a stdout notice

0 commit comments

Comments
 (0)