fix(cli): render the Copilot manifest through the memory core

The PostToolUse change rendered the manifest twice per install and uninstall
(current and previous generation) and freed both with free(), which grew the
raw allocator count in src/cli/cli.c past its baseline (lint-memory-core).
The manifest is now written with cbm_json_writer_allocator() and released with
cbm_free(CBM_MEM_CLASS_OTHER, ...), including the frees that predate the
change, so cli.c drops from 170 to 166 raw sites and its baseline is lowered
to match. The rendered bytes are unchanged, so byte-exact ownership of
manifests written by earlier installs still holds.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-10-25 20:57:54 +01:00
1 parent d0937940a9
commit 5350b5b83e
4 files changed
+24 -18

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ internal/cbm/sqlite_writer.c 74
src/cli/activation_transaction.c 80
src/cli/agent_clients.c 73
src/cli/agent_profiles.c 7
src/cli/cli.c 170
src/cli/cli.c 166
src/cli/client_adapter.c 3
src/cli/config_edit_path.c 2
src/cli/config_json_like.c 142
+14 -10
View File
@@ -22,6 +22,7 @@
#include "foundation/compat.h"
#include "foundation/platform.h"
#include "foundation/constants.h"
#include "foundation/json_writer.h"
#include "foundation/log.h"
#include "foundation/mem_core.h"
#include "foundation/sha256.h"
@@ -4242,7 +4243,8 @@ static bool cbm_copilot_add_hook_event(yyjson_mut_doc *doc, yyjson_mut_val *hook
/* include_posttool renders the current manifest; without it the function
* renders the previous lifecycle-only manifest, which installs up to v0.12 wrote
* and which upgrades and uninstalls must still recognize as owned. */
* and which upgrades and uninstalls must still recognize as owned. The result is
* released with cbm_free(CBM_MEM_CLASS_OTHER, ...). */
static char *cbm_build_copilot_hook_manifest(const char *binary_path, bool include_posttool) {
char session_bash[CLI_BUF_8K];
char session_powershell[CLI_BUF_8K];
@@ -4284,7 +4286,9 @@ static char *cbm_build_copilot_hook_manifest(const char *binary_path, bool inclu
cbm_copilot_add_hook_event(doc, hooks, "PostToolUse", COPILOT_SEARCH_MATCHER,
post_bash, post_powershell)) &&
yyjson_mut_obj_add_val(doc, root, "hooks", hooks);
char *manifest = ok ? yyjson_mut_write(doc, YYJSON_WRITE_PRETTY, NULL) : NULL;
char *manifest = ok ? yyjson_mut_write_opts(doc, YYJSON_WRITE_PRETTY,
cbm_json_writer_allocator(), NULL, NULL)
: NULL;
yyjson_mut_doc_free(doc);
return manifest;
}
@@ -4303,14 +4307,14 @@ static int cbm_upsert_copilot_hooks(const char *binary_path, const char *manifes
char *manifest = cbm_build_copilot_hook_manifest(binary_path, true);
char *previous = cbm_build_copilot_hook_manifest(binary_path, false);
if (!manifest || !previous || !manifest_path) {
free(manifest);
free(previous);
cbm_free(CBM_MEM_CLASS_OTHER, manifest);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
return CLI_ERR;
}
const char *const released[] = {previous};
int rc = cbm_text_migrate_owned_document(manifest_path, manifest, released, 1U);
free(manifest);
free(previous);
cbm_free(CBM_MEM_CLASS_OTHER, manifest);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
return rc == 0 ? CLI_OK : CLI_ERR;
}
@@ -4318,14 +4322,14 @@ static int cbm_remove_copilot_hooks(const char *manifest_path, const char *binar
char *manifest = cbm_build_copilot_hook_manifest(binary_path, true);
char *previous = cbm_build_copilot_hook_manifest(binary_path, false);
if (!manifest || !previous || !manifest_path) {
free(manifest);
free(previous);
cbm_free(CBM_MEM_CLASS_OTHER, manifest);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
return CLI_ERR;
}
const char *const released[] = {previous};
int rc = cbm_text_remove_owned_document_any(manifest_path, manifest, released, 1U);
free(manifest);
free(previous);
cbm_free(CBM_MEM_CLASS_OTHER, manifest);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
return rc >= 0 ? CLI_OK : CLI_ERR;
}
+2 -1
View File
@@ -283,7 +283,8 @@ bool cbm_hook_path_contains_for_testing(const char *root, const char *candidate,
bool case_insensitive);
const char *cbm_hook_no_project_index_guidance_for_testing(const char *event);
bool cbm_hook_augment_parse_bash_pattern_for_testing(const char *cmd, char *out, size_t out_sz);
/* include_posttool false renders the previous lifecycle-only manifest. */
/* include_posttool false renders the previous lifecycle-only manifest. Release
* the result with cbm_free(CBM_MEM_CLASS_OTHER, ...). */
char *cbm_build_copilot_hook_manifest_for_testing(const char *binary_path, bool include_posttool);
bool cbm_mcp_command_path_probe_safe_for_testing(const char *command, bool windows);
void cbm_set_mcp_command_path_probe_counter_for_testing(int *counter);
+7 -6
View File
@@ -23,6 +23,7 @@
#include <daemon/version_cohort.h>
#include <foundation/constants.h>
#include <foundation/log.h>
#include <foundation/mem_core.h>
#include <foundation/platform.h>
#include <foundation/private_file_lock.h>
#include <foundation/sha256.h>
@@ -12907,8 +12908,8 @@ TEST(cli_copilot_manifest_adds_posttooluse_search_hook) {
bool previous_shape = strstr(previous, "sessionStart") && strstr(previous, "subagentStart") &&
!strstr(previous, "PostToolUse");
yyjson_doc_free(doc);
free(current);
free(previous);
cbm_free(CBM_MEM_CLASS_OTHER, current);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
if (!shaped || !previous_shape)
FAIL("Copilot manifest must add one PostToolUse search hook beside the lifecycle pair");
PASS();
@@ -12939,8 +12940,8 @@ TEST(cli_copilot_install_upgrades_previous_manifest) {
char *previous = cbm_build_copilot_hook_manifest_for_testing(binary, false);
char *current = cbm_build_copilot_hook_manifest_for_testing(binary, true);
if (!previous || !current) {
free(previous);
free(current);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
cbm_free(CBM_MEM_CLASS_OTHER, current);
test_rmdir_r(tmpdir);
FAIL("Copilot manifest rendering failed");
}
@@ -12967,8 +12968,8 @@ TEST(cli_copilot_install_upgrades_previous_manifest) {
free(upgraded);
free(again);
free(previous);
free(current);
cbm_free(CBM_MEM_CLASS_OTHER, previous);
cbm_free(CBM_MEM_CLASS_OTHER, current);
restore_test_env("HOME", saved_home);
restore_test_env("PATH", saved_path);
restore_test_env("COPILOT_HOME", saved_copilot);