Skip to content

Commit 23dfea5

Browse files
committed
fix(hook-augment): register Bash search matcher
Wire Bash into the installed Claude PreToolUse matcher, add end-to-end reachability coverage, migrate the previous Grep|Glob matcher, and remove the unused rtk_grep parameter. Signed-off-by: Jake Abendroth <contact@jakea.net>
1 parent 5882be2 commit 23dfea5

4 files changed

Lines changed: 55 additions & 12 deletions

File tree

‎src/cli/cli.c‎

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

38203820
/* ── Claude Code pre-tool hooks ───────────────────────────────── */
38213821

3822-
/* Search augmentation runs before Grep/Glob; exact coverage context runs after
3822+
/* Search augmentation runs before Grep/Glob/Bash; exact coverage context runs after
38233823
* Read. Both adapters are context-only and fail open. */
3824-
#define CMM_HOOK_SEARCH_MATCHER "Grep|Glob"
3824+
#define CMM_HOOK_SEARCH_MATCHER "Grep|Glob|Bash"
38253825
#define CMM_HOOK_READ_MATCHER "Read"
38263826
/* Basename only; the full command path is resolved at install time via
38273827
* cbm_resolve_hook_command so $CLAUDE_CONFIG_DIR is honored. */
@@ -3845,6 +3845,7 @@ static int cbm_remove_vibe_mcp_owned(const char *binary_path, const char *config
38453845
static const char *const cmm_claude_old_matchers[] = {
38463846
"Grep|Glob|Read|Search",
38473847
"Grep|Glob|Read",
3848+
"Grep|Glob",
38483849
NULL,
38493850
};
38503851
static const char *const cmm_gemini_old_matchers[] = {
@@ -4961,7 +4962,7 @@ static int cbm_remove_owned_hook_script(const char *path, const char *expected_c
49614962

49624963
/* Install the search-augmenter shim to ~/.claude/hooks/.
49634964
* The shim is a thin wrapper that delegates to `<binary> hook-augment`,
4964-
* which adds graph context to Grep/Glob calls. It NEVER blocks a tool call:
4965+
* which adds graph context to Grep/Glob/Bash search calls. It NEVER blocks a tool call:
49654966
* a missing/old/hung binary results in a silent exit 0 (issue #362/#288).
49664967
* The legacy filename `cbm-code-discovery-gate` is retained so existing
49674968
* settings.json entries and uninstall keep working with zero migration. */
@@ -7204,7 +7205,7 @@ static void install_claude_code_config(const char *home, const char *binary_path
72047205
}
72057206
}
72067207
if (gate_ok) {
7207-
printf(" hooks: PreToolUse Grep/Glob search augmentation + PostToolUse Read coverage "
7208+
printf(" hooks: PreToolUse Grep/Glob/Bash search augmentation + PostToolUse Read coverage "
72087209
"(non-blocking)\n");
72097210
}
72107211
if (session_ok) {

‎src/cli/cli.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ int cbm_cmd_config(int argc, char **argv);
459459

460460
/* hook-augment: stdin-driven Claude Code PreToolUse augmenter.
461461
* Reads the hook JSON from stdin and emits hookSpecificOutput.additionalContext
462-
* with search_graph hits for Grep/Glob calls. NEVER blocks: every failure
462+
* with search_graph hits for Grep/Glob/Bash search calls. NEVER blocks: every failure
463463
* path returns 0 with no stdout output. */
464464
int cbm_cmd_hook_augment(int argc, char **argv);
465465

‎src/cli/hook_augment.c‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*
1414
* The underlying query is `search_graph` (pure SQLite, shell-free) — chosen
1515
* over `search_code` (which shells out to grep|xargs) so the hook stays cheap
16-
* enough to run before every Grep/Glob.
16+
* enough to run before every Grep/Glob/Bash call.
1717
*/
1818

1919
#include "cli/cli.h"
@@ -1079,7 +1079,7 @@ static bool ha_is_env_assign(const char *t) {
10791079

10801080
typedef enum { HA_BIN_GREP, HA_BIN_RG, HA_BIN_AG, HA_BIN_ACK, HA_BIN_UGREP } ha_bin_t;
10811081

1082-
static const char *ha_search_bin_val_flags(ha_bin_t bin, bool rtk_grep) {
1082+
static const char *ha_search_bin_val_flags(ha_bin_t bin) {
10831083
switch (bin) {
10841084
case HA_BIN_RG:
10851085
return "ABCmtTgMP";
@@ -1147,7 +1147,7 @@ static bool ha_parse_bash_search_pattern(const char *cmd, char *out, size_t out_
11471147
return false;
11481148
}
11491149

1150-
const char *val_flags = ha_search_bin_val_flags(bin, rtk && bin == HA_BIN_GREP);
1150+
const char *val_flags = ha_search_bin_val_flags(bin);
11511151
const char *pattern = NULL;
11521152
int e_count = 0;
11531153
bool end_of_flags = false;

‎tests/test_cli.c‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <foundation/constants.h>
2222
#include <foundation/platform.h>
2323
#include <mcp/mcp.h>
24+
#include <pipeline/pipeline.h>
2425
#include <foundation/yaml.h>
2526
#include <store/store.h>
2627
#include <yyjson/yyjson.h>
@@ -8791,6 +8792,44 @@ TEST(cli_hook_augment_context_tracks_search_json_shape) {
87918792
PASS();
87928793
}
87938794

8795+
/* Exercise the real PreToolUse + Bash route instead of calling only the
8796+
* tokenizer seam. This pins the event guard and the graph lookup path together.
8797+
*/
8798+
TEST(cli_hook_augment_bash_pretooluse_reaches_augmenter) {
8799+
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
8800+
ASSERT_NOT_NULL(srv);
8801+
cbm_store_t *st = cbm_mcp_server_store(srv);
8802+
ASSERT_NOT_NULL(st);
8803+
char *project = cbm_project_name_from_path("/tmp/hookproj");
8804+
ASSERT_NOT_NULL(project);
8805+
cbm_mcp_server_set_project(srv, project);
8806+
cbm_store_upsert_project(st, project, "/tmp/hookproj");
8807+
char qualified_name[256];
8808+
snprintf(qualified_name, sizeof(qualified_name), "%s.mod.someIndexedSymbol", project);
8809+
cbm_node_t node = {.project = project,
8810+
.label = "Function",
8811+
.name = "someIndexedSymbol",
8812+
.qualified_name = qualified_name,
8813+
.file_path = "mod.py",
8814+
.start_line = 1,
8815+
.end_line = 4};
8816+
ASSERT_GT(cbm_store_upsert_node(st, &node), 0);
8817+
8818+
const char *input = "{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Bash\","
8819+
"\"cwd\":\"/tmp/hookproj\",\"tool_input\":{"
8820+
"\"command\":\"rg -n someIndexedSymbol .\"}}";
8821+
char *output = cbm_hook_augment_process(srv, input);
8822+
bool reached = output && strstr(output, "hookSpecificOutput") &&
8823+
strstr(output, "someIndexedSymbol") && strstr(output, "mod.py");
8824+
free(output);
8825+
cbm_mcp_server_free(srv);
8826+
free(project);
8827+
8828+
if (!reached)
8829+
FAIL("PreToolUse Bash must reach graph search augmentation");
8830+
PASS();
8831+
}
8832+
87948833
TEST(cli_hook_augment_bash_pattern_extractor) {
87958834
char out[256];
87968835

@@ -10439,7 +10478,7 @@ TEST(cli_upsert_claude_hook_fresh) {
1043910478
ASSERT_NOT_NULL(data);
1044010479
ASSERT(strstr(data, "PreToolUse") != NULL);
1044110480
ASSERT(strstr(data, "PostToolUse") != NULL);
10442-
ASSERT(strstr(data, "\"Grep|Glob\"") != NULL);
10481+
ASSERT(strstr(data, "\"Grep|Glob|Bash\"") != NULL);
1044310482
ASSERT(strstr(data, "\"Read\"") != NULL);
1044410483
ASSERT(strstr(data, "\"Grep|Glob|Read\"") == NULL);
1044510484
ASSERT_EQ(test_count_substring(data, "cbm-code-discovery-gate"), 2U);
@@ -10953,7 +10992,7 @@ TEST(cli_upsert_claude_hook_existing) {
1095310992

1095410993
const char *data = read_test_file(settingspath);
1095510994
ASSERT_NOT_NULL(data);
10956-
ASSERT(strstr(data, "\"Grep|Glob\"") != NULL);
10995+
ASSERT(strstr(data, "\"Grep|Glob|Bash\"") != NULL);
1095710996
ASSERT(strstr(data, "PostToolUse") != NULL);
1095810997
ASSERT(strstr(data, "\"Read\"") != NULL);
1095910998
/* Existing hook preserved */
@@ -11034,7 +11073,8 @@ TEST(cli_upsert_claude_hook_replace) {
1103411073
const char *data = read_test_file(settingspath);
1103511074
ASSERT_NOT_NULL(data);
1103611075
ASSERT(strstr(data, "\"Grep|Glob|Read\"") == NULL);
11037-
ASSERT(strstr(data, "\"Grep|Glob\"") != NULL);
11076+
ASSERT(strstr(data, "\"Grep|Glob|Bash\"") != NULL);
11077+
ASSERT(strstr(data, "\"Grep|Glob\"") == NULL);
1103811078
ASSERT(strstr(data, "PostToolUse") != NULL);
1103911079
ASSERT_EQ(test_count_substring(data, "cbm-code-discovery-gate"), 2U);
1104011080

@@ -11086,7 +11126,8 @@ TEST(cli_remove_claude_hooks) {
1108611126

1108711127
const char *data = read_test_file(settingspath);
1108811128
ASSERT_NOT_NULL(data);
11089-
ASSERT(strstr(data, "Grep|Glob|Read") == NULL);
11129+
ASSERT(strstr(data, "\"Grep|Glob|Bash\"") == NULL);
11130+
ASSERT(strstr(data, "\"Grep|Glob\"") == NULL);
1109011131
ASSERT(strstr(data, "cbm-code-discovery-gate") == NULL);
1109111132

1109211133
test_rmdir_r(tmpdir);
@@ -12039,6 +12080,7 @@ SUITE(cli) {
1203912080
RUN_TEST(cli_claude_hook_commands_shell_quote_custom_config_dir);
1204012081
RUN_TEST(cli_codex_migrates_to_single_hook_representation);
1204112082
RUN_TEST(cli_hook_augment_context_tracks_search_json_shape);
12083+
RUN_TEST(cli_hook_augment_bash_pretooluse_reaches_augmenter);
1204212084
RUN_TEST(cli_hook_augment_bash_pattern_extractor);
1204312085
RUN_TEST(cli_hook_augment_lifecycle_output_contract);
1204412086
RUN_TEST(cli_hook_augment_subagent_tier_router_contract);

0 commit comments

Comments
 (0)