Summary
The manage_adr tool's get path (default mode) contains a use-after-free bug in handle_manage_adr (src/mcp/mcp.c) that causes the MCP client to hang indefinitely when an adr.md file exists.
Root Cause
// Buggy code in handle_manage_adr (get branch):
char *buf = malloc(sz + 1);
size_t n = fread(buf, 1, sz, fp);
buf[n] = '\0';
(void)fclose(fp);
yyjson_mut_obj_add_str(doc, root_obj, "content", buf);
free(buf); // BUG: yyjson stores the raw pointer, not a copy
// ... later:
char *json = yy_doc_to_str(doc); // reads freed memory → garbage JSON
yyjson_mut_obj_add_str stores the raw pointer without copying. After free(buf), the yyjson document holds a dangling pointer into freed heap memory. When yy_doc_to_str(doc) runs, the allocator has overwritten that memory with bookkeeping bytes, producing invalid JSON. cbm_jsonrpc_format_response then calls yyjson_read() on the corrupted string; the parse fails silently (res_doc == NULL), so no "result" field is added.
Symptom
The MCP response becomes {"jsonrpc":"2.0","id":N} — missing result. The MCP client (Claude Code and others) waits indefinitely for a valid response.
Affected Scope
- Only the
get branch (default when mode is absent or "get")
- Only when a
.codebase-memory/adr.md file exists for the project
- Projects without an ADR file are unaffected (
no_adr path uses string literals)
Fix
Hoist adr_buf to function scope (initialized NULL), remove the premature free, and free it after yy_doc_to_str has serialized the document:
char *adr_buf = NULL; /* freed after yy_doc_to_str — yyjson holds pointer, not copy */
// ...
adr_buf = malloc(sz + 1);
// ...
yyjson_mut_obj_add_str(doc, root_obj, "content", adr_buf);
/* do NOT free adr_buf here: yyjson stores the pointer, not a copy */
// ...
char *json = yy_doc_to_str(doc);
yyjson_mut_doc_free(doc);
free(adr_buf); /* safe to free now — doc has been serialized */
Total diff: ~7 lines in handle_manage_adr.
Regression Test
A regression test tool_manage_adr_get_with_existing_adr is included that:
- Creates a temp directory with
.codebase-memory/adr.md
- Registers it as a project in an in-memory store
- Calls
manage_adr(mode="get") via cbm_mcp_server_handle
- Asserts the JSON-RPC response contains
"result" with the ADR content
This test fails on main (before the fix) and passes after.
Summary
The
manage_adrtool'sgetpath (default mode) contains a use-after-free bug inhandle_manage_adr(src/mcp/mcp.c) that causes the MCP client to hang indefinitely when anadr.mdfile exists.Root Cause
yyjson_mut_obj_add_strstores the raw pointer without copying. Afterfree(buf), the yyjson document holds a dangling pointer into freed heap memory. Whenyy_doc_to_str(doc)runs, the allocator has overwritten that memory with bookkeeping bytes, producing invalid JSON.cbm_jsonrpc_format_responsethen callsyyjson_read()on the corrupted string; the parse fails silently (res_doc == NULL), so no"result"field is added.Symptom
The MCP response becomes
{"jsonrpc":"2.0","id":N}— missingresult. The MCP client (Claude Code and others) waits indefinitely for a valid response.Affected Scope
getbranch (default whenmodeis absent or"get").codebase-memory/adr.mdfile exists for the projectno_adrpath uses string literals)Fix
Hoist
adr_bufto function scope (initializedNULL), remove the prematurefree, and free it afteryy_doc_to_strhas serialized the document:Total diff: ~7 lines in
handle_manage_adr.Regression Test
A regression test
tool_manage_adr_get_with_existing_adris included that:.codebase-memory/adr.mdmanage_adr(mode="get")viacbm_mcp_server_handle"result"with the ADR contentThis test fails on
main(before the fix) and passes after.