mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
perf(hook-augment): gate no-op Bash calls before identity hashing
Maintainer commit on top of the Bash-matcher work, closing the cost question the 2026-08-03 review left open before default-on shipped. Measured on a production binary (2026-08-28): every hook invocation paid ~1.1-1.2 s of user CPU BEFORE any gating — not in cbm_mcp_server_new as first suspected, but in main()'s executable-identity hash for cohort admission, which the hook-client path runs ahead of any input inspection. With Bash in the installed matcher, nearly every agent command (`ls`, `make`, `git`, ...) would have paid that for nothing: agents issue far more Bash calls than Grep/Glob, and almost none are searches. The fix: main() now reads stdin on the hook-client path and exits before identity hashing when the event can never produce output — an un-forced PreToolUse Bash event whose command yields no search pattern or no queryable token. A no-op touches no shared state, so it needs no identity. The gate is a pure exported function using the exact parser pair ha_process uses, so gate and augmenter cannot disagree; anything uncertain (lifecycle events, other tools, coverage adapters, oversized stdin, forced events) pays full fare unchanged. Consumed stdin is handed back through a prefetch so downstream readers see it exactly once. Measured after: no-op Bash calls ~10 ms wall / 0.00 s user CPU (was ~1.2 s); real Bash searches and Grep/Glob unchanged at full fare. cli 259/0, mcp 179/0. Co-authored-by: Jake Abendroth <contact@jakea.net> Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
36b7b18b4d
commit
09c6624166
3 files changed
+75
No files matched your search
@@ -477,6 +477,10 @@ char *cbm_hook_augment_lifecycle_json_for(const char *input, const char *forced_
|
||||
* hard fail-open deadline without constructing a local MCP/store instance. */
|
||||
void cbm_hook_augment_arm_deadline(void);
|
||||
char *cbm_hook_augment_read_stdin(void);
|
||||
/* Pure no-op gate for the hook-client fast path (see hook_augment.c). */
|
||||
bool cbm_hook_augment_input_is_noop_bash(const char *input);
|
||||
/* Hand back stdin bytes main() consumed early; the next read returns them. */
|
||||
void cbm_hook_augment_prefetch_stdin(char *owned);
|
||||
|
||||
/* Process one already-read hook payload using a caller-owned MCP session.
|
||||
* Returns a malloc-owned hook output JSON string, or NULL for fail-open/no
|
||||
|
||||
@@ -154,7 +154,22 @@ void cbm_hook_augment_arm_deadline(void) {
|
||||
|
||||
/* ── stdin ────────────────────────────────────────────────────────── */
|
||||
|
||||
/* main() reads stdin EARLY on the hook-client path so the no-op gate can run
|
||||
* before executable-identity hashing; the consumed bytes are handed back here
|
||||
* so every downstream reader sees them exactly once. */
|
||||
static char *g_ha_prefetched_stdin = NULL;
|
||||
|
||||
void cbm_hook_augment_prefetch_stdin(char *owned) {
|
||||
free(g_ha_prefetched_stdin);
|
||||
g_ha_prefetched_stdin = owned;
|
||||
}
|
||||
|
||||
char *cbm_hook_augment_read_stdin(void) {
|
||||
if (g_ha_prefetched_stdin) {
|
||||
char *out = g_ha_prefetched_stdin;
|
||||
g_ha_prefetched_stdin = NULL;
|
||||
return out;
|
||||
}
|
||||
char *buf = malloc(HA_STDIN_CAP + 1);
|
||||
if (!buf) {
|
||||
return NULL;
|
||||
@@ -1617,6 +1632,40 @@ char *cbm_hook_augment_process_for(cbm_mcp_server_t *srv, const char *input_json
|
||||
return ha_process(srv, input_json, forced_event, dialect);
|
||||
}
|
||||
|
||||
/* TRUE iff `input` is an un-forced PreToolUse Bash event whose command can
|
||||
* never produce augmentation output: not a recognised search, or no queryable
|
||||
* token. Pure string work — safe to run BEFORE executable-identity hashing,
|
||||
* cohort admission, or any daemon contact, which is the point: agents issue
|
||||
* far more Bash calls than Grep/Glob and nearly all are not searches, while
|
||||
* the identity hash alone costs ~1.1 s of user CPU per invocation on a
|
||||
* production binary (measured 2026-08-28). Uses the exact parser pair
|
||||
* ha_process uses, so the gate and the augmenter cannot disagree about what
|
||||
* counts as a search. Anything uncertain returns false and pays full fare:
|
||||
* lifecycle events, other tools, coverage adapters, oversized stdin. */
|
||||
bool cbm_hook_augment_input_is_noop_bash(const char *input) {
|
||||
if (!input || strlen(input) > HA_STDIN_CAP) {
|
||||
return false;
|
||||
}
|
||||
yyjson_doc *doc = yyjson_read(input, strlen(input), 0);
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
bool noop = false;
|
||||
yyjson_val *root = yyjson_doc_get_root(doc);
|
||||
const char *event = ha_hook_event_name(root);
|
||||
const char *tool = ha_obj_str(root, "tool_name");
|
||||
if (event && tool && strcmp(event, "PreToolUse") == 0 && strcmp(tool, "Bash") == 0) {
|
||||
yyjson_val *tin = yyjson_obj_get(root, "tool_input");
|
||||
const char *cmd = ha_obj_str(tin, "command");
|
||||
char pat[HA_BASH_TOK_SZ];
|
||||
char tok[HA_MAX_TOKEN + 1];
|
||||
noop = !ha_parse_bash_search_pattern(cmd, pat, sizeof(pat)) ||
|
||||
!ha_extract_token(pat, tok, sizeof(tok));
|
||||
}
|
||||
yyjson_doc_free(doc);
|
||||
return noop;
|
||||
}
|
||||
|
||||
int cbm_cmd_hook_augment(int argc, char **argv) {
|
||||
cbm_hook_augment_arm_deadline();
|
||||
|
||||
@@ -1642,6 +1691,10 @@ int cbm_cmd_hook_augment(int argc, char **argv) {
|
||||
if (!input) {
|
||||
return 0;
|
||||
}
|
||||
if (!forced_event && cbm_hook_augment_input_is_noop_bash(input)) {
|
||||
free(input);
|
||||
return 0;
|
||||
}
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
char *output = srv ? ha_process(srv, input, forced_event, dialect) : NULL;
|
||||
if (output) {
|
||||
|
||||
+18
@@ -1791,6 +1791,24 @@ int main(int argc, char **argv) {
|
||||
#ifndef _WIN32
|
||||
cbm_hook_augment_arm_deadline();
|
||||
#endif
|
||||
/* Read stdin now and bail before executable-identity hashing when the
|
||||
* event can never produce output (an un-forced PreToolUse Bash call
|
||||
* that is not a search). The identity hash costs ~1.1 s of user CPU
|
||||
* per invocation, and with Bash in the installed matcher nearly every
|
||||
* agent command would pay it for nothing. A no-op touches no shared
|
||||
* state, so it needs no identity. Anything else is handed back via
|
||||
* the prefetch so downstream readers see stdin exactly once. */
|
||||
if (!hook_event) {
|
||||
char *hook_input = cbm_hook_augment_read_stdin();
|
||||
if (!hook_input) {
|
||||
return EXIT_SUCCESS; /* fail open, nothing to augment */
|
||||
}
|
||||
if (cbm_hook_augment_input_is_noop_bash(hook_input)) {
|
||||
free(hook_input);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
cbm_hook_augment_prefetch_stdin(hook_input);
|
||||
}
|
||||
}
|
||||
|
||||
if (role == CBM_DAEMON_PROCESS_STATELESS) {
|
||||
|
||||
Reference in new issue
Block a user