mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2822 from divakantpandey/fix/2722-install-managed-binary-path
fix(cli): use managed binary path for agent configs when externally managed (#2722)
This commit is contained in:
3 files changed
+117
-20
No files matched your search
+55
-20
@@ -10988,7 +10988,20 @@ void cbm_cli_set_client_selection_for_testing(const char *spec) {
|
||||
/* Detect the running binary's path at runtime. The return value distinguishes
|
||||
* OS-reported identity (safe ownership evidence) from the ~/.local/bin fallback
|
||||
* used only as an install copy source. */
|
||||
#ifdef CBM_CLI_ENABLE_TEST_API
|
||||
static const char *g_cli_self_path_override_for_test = NULL;
|
||||
void cbm_cli_set_self_path_override_for_test(const char *self_path) {
|
||||
g_cli_self_path_override_for_test = self_path;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool cbm_detect_self_path(char *buf, size_t buf_sz, const char *home) {
|
||||
#ifdef CBM_CLI_ENABLE_TEST_API
|
||||
if (g_cli_self_path_override_for_test) {
|
||||
snprintf(buf, buf_sz, "%s", g_cli_self_path_override_for_test);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
buf[0] = '\0';
|
||||
bool exact = false;
|
||||
#ifdef _WIN32
|
||||
@@ -11250,6 +11263,7 @@ char *cbm_build_install_plan_json(const char *home, const char *binary_path) {
|
||||
|
||||
typedef struct {
|
||||
const char *bin_target;
|
||||
const char *config_binary;
|
||||
const char *bin_dir;
|
||||
const char *home;
|
||||
const char *shell_rc;
|
||||
@@ -11274,6 +11288,8 @@ static int cli_install_activate(void *opaque) {
|
||||
!activation->shell_rc) {
|
||||
return CLI_TRUE;
|
||||
}
|
||||
const char *config_binary =
|
||||
activation->config_binary ? activation->config_binary : activation->bin_target;
|
||||
char previous_managed_binary[CLI_BUF_1K] = {0};
|
||||
bool previous_managed_binary_exact = cbm_detect_self_path(
|
||||
previous_managed_binary, sizeof(previous_managed_binary), activation->home);
|
||||
@@ -11322,10 +11338,13 @@ static int cli_install_activate(void *opaque) {
|
||||
* including same-binary and non-force installs. */
|
||||
int agent_config_rc = CLI_OK;
|
||||
if (!activation->skip_config) {
|
||||
const char *prev = previous_managed_binary_exact ? previous_managed_binary : NULL;
|
||||
if (config_binary != activation->bin_target &&
|
||||
(!prev || strcmp(prev, config_binary) == 0)) {
|
||||
prev = activation->bin_target;
|
||||
}
|
||||
agent_config_rc = cbm_install_agent_configs_with_previous(
|
||||
activation->home, activation->bin_target,
|
||||
previous_managed_binary_exact ? previous_managed_binary : NULL, activation->force,
|
||||
activation->dry_run);
|
||||
activation->home, config_binary, prev, activation->force, activation->dry_run);
|
||||
}
|
||||
if (agent_config_rc != CLI_OK) {
|
||||
cli_activation_transaction_finalize_committed_or_fail_stop(
|
||||
@@ -11472,11 +11491,40 @@ int cbm_cmd_install(int argc, char **argv) {
|
||||
return CLI_TRUE;
|
||||
}
|
||||
|
||||
char self_path[CLI_BUF_1K] = {0};
|
||||
bool self_path_exact = cbm_detect_self_path(self_path, sizeof(self_path), home);
|
||||
|
||||
/* #1566: a binary owned by mise/Homebrew/nix is not ours to relocate. Infer
|
||||
* it, and let either flag override the inference in either direction. */
|
||||
bool externally_managed =
|
||||
!force_binary && cli_binary_is_externally_managed(self_path, self_path_exact, bin_dir);
|
||||
bool explicit_skip_binary = skip_binary;
|
||||
if (externally_managed) {
|
||||
skip_binary = true;
|
||||
}
|
||||
|
||||
/* NOT stat(): on Windows it goes through the ANSI code page, so an
|
||||
* extended-length or non-ASCII target reports "absent" and a non-force
|
||||
* install silently overwrites bytes the user asked to keep. */
|
||||
cbm_path_info_t target_status;
|
||||
bool target_exists = cbm_path_info_utf8(bin_target, &target_status) == 0;
|
||||
bool same_binary = cbm_same_file(self_path, bin_target);
|
||||
|
||||
/* #2722: when a binary is managed externally (mise, brew, nix, etc.),
|
||||
* do not write the non-existent default target into agent configurations.
|
||||
* Use the running managed binary path instead. */
|
||||
const char *config_binary = bin_target;
|
||||
if (externally_managed && self_path_exact && self_path[0]) {
|
||||
config_binary = self_path;
|
||||
} else if (skip_binary && !target_exists && self_path_exact && self_path[0]) {
|
||||
config_binary = self_path;
|
||||
}
|
||||
|
||||
/* --plan: emit the machine-readable install receipt and exit WITHOUT
|
||||
* mutating anything (no config writes, no index deletion, no network) so
|
||||
* an agent can inspect exactly what install would touch first (#388). */
|
||||
if (plan) {
|
||||
char *json = cbm_build_install_plan_json_options(home, bin_target, skip_config);
|
||||
char *json = cbm_build_install_plan_json_options(home, config_binary, skip_config);
|
||||
if (!json) {
|
||||
(void)fprintf(stderr, "error: failed to build install plan\n");
|
||||
return CLI_TRUE;
|
||||
@@ -11498,16 +11546,9 @@ int cbm_cmd_install(int argc, char **argv) {
|
||||
|
||||
printf("codebase-memory-mcp install %s\n\n", CBM_VERSION);
|
||||
|
||||
char self_path[CLI_BUF_1K] = {0};
|
||||
bool self_path_exact = cbm_detect_self_path(self_path, sizeof(self_path), home);
|
||||
|
||||
/* #1566: a binary owned by mise/Homebrew/nix is not ours to relocate. Infer
|
||||
* it, and let either flag override the inference in either direction. */
|
||||
bool externally_managed =
|
||||
!force_binary && cli_binary_is_externally_managed(self_path, self_path_exact, bin_dir);
|
||||
if (externally_managed || skip_binary) {
|
||||
if (externally_managed || explicit_skip_binary) {
|
||||
const char *manager = cli_external_manager_name(self_path);
|
||||
if (skip_binary) {
|
||||
if (explicit_skip_binary) {
|
||||
printf("Skipping binary placement (--skip-binary); configuring agents only.\n\n");
|
||||
} else {
|
||||
printf("Binary is managed elsewhere%s%s:\n %s\n"
|
||||
@@ -11515,15 +11556,8 @@ int cbm_cmd_install(int argc, char **argv) {
|
||||
" (use --force-binary to install a copy into %s anyway)\n\n",
|
||||
manager ? " by " : "", manager ? manager : "", self_path, bin_dir);
|
||||
}
|
||||
skip_binary = true;
|
||||
}
|
||||
|
||||
/* NOT stat(): on Windows it goes through the ANSI code page, so an
|
||||
* extended-length or non-ASCII target reports "absent" and a non-force
|
||||
* install silently overwrites bytes the user asked to keep. */
|
||||
cbm_path_info_t target_status;
|
||||
bool target_exists = cbm_path_info_utf8(bin_target, &target_status) == 0;
|
||||
bool same_binary = cbm_same_file(self_path, bin_target);
|
||||
bool do_copy = !skip_binary && !same_binary && (!target_exists || force);
|
||||
|
||||
/* (#607) Default: preserve existing indexes. `--reset-indexes` opts into
|
||||
@@ -11690,6 +11724,7 @@ int cbm_cmd_install(int argc, char **argv) {
|
||||
#endif
|
||||
cli_install_activation_t activation = {
|
||||
.bin_target = bin_target,
|
||||
.config_binary = config_binary,
|
||||
.bin_dir = bin_dir,
|
||||
.home = home,
|
||||
.shell_rc = shell_rc,
|
||||
|
||||
@@ -250,6 +250,8 @@ void cbm_cli_set_client_selection_for_testing(const char *spec);
|
||||
* not recognisably foreign. Detection must be positive evidence — see the
|
||||
* implementation comment. */
|
||||
const char *cbm_cli_external_manager_name_for_testing(const char *self_path);
|
||||
/* #2722: override runtime binary self-path detection for testing. */
|
||||
void cbm_cli_set_self_path_override_for_test(const char *self_path);
|
||||
/* #1558: expose the config-key table so a test can prove a key is discoverable
|
||||
* (`config list`/`set` both walk it). */
|
||||
size_t cbm_cli_config_key_count_for_testing(void);
|
||||
|
||||
@@ -18673,6 +18673,65 @@ TEST(cli_external_manager_detection_needs_positive_evidence_issue1566) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #2722: when a binary is managed externally (e.g. mise, brew, nix), `install`
|
||||
* skips placing a binary at ~/.local/bin, but must write the running managed
|
||||
* binary path into agent configuration files instead of the non-existent
|
||||
* default target. */
|
||||
TEST(cli_install_managed_binary_config_path_issue2722) {
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-install-managed-XXXXXX");
|
||||
if (!cbm_mkdtemp(tmpdir)) {
|
||||
FAIL("cbm_mkdtemp failed");
|
||||
}
|
||||
char *old_home = NULL;
|
||||
char *old_cache = NULL;
|
||||
cli_activation_save_env(&old_home, &old_cache);
|
||||
cbm_setenv("HOME", tmpdir, 1);
|
||||
char cache_dir[512];
|
||||
snprintf(cache_dir, sizeof(cache_dir), "%s/cache", tmpdir);
|
||||
cbm_setenv("CBM_CACHE_DIR", cache_dir, 1);
|
||||
|
||||
test_mark_claude_code_installed(tmpdir);
|
||||
|
||||
const char *managed_path =
|
||||
"/Users/x/.local/share/mise/installs/cbm/0.12.1/bin/codebase-memory-mcp";
|
||||
cbm_cli_set_self_path_override_for_test(managed_path);
|
||||
|
||||
cli_activation_fake_t fake = {
|
||||
.mutation_reserve_result = 1,
|
||||
};
|
||||
cbm_cli_activation_ops_t ops = cli_activation_fake_ops(&fake);
|
||||
cbm_cli_set_activation_ops_for_test(&ops);
|
||||
|
||||
char *argv[] = {"--clients=claude"};
|
||||
int rc = cli_test_cmd_install(1, argv);
|
||||
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
cbm_cli_set_self_path_override_for_test(NULL);
|
||||
|
||||
char bin_target[512];
|
||||
snprintf(bin_target, sizeof(bin_target), "%s/.local/bin/codebase-memory-mcp", tmpdir);
|
||||
struct stat target_st;
|
||||
bool target_created = stat(bin_target, &target_st) == 0;
|
||||
|
||||
char claude_json_path[512];
|
||||
snprintf(claude_json_path, sizeof(claude_json_path), "%s/.claude.json", tmpdir);
|
||||
char *claude_json = read_test_file_alloc(claude_json_path);
|
||||
bool has_managed_path = claude_json && strstr(claude_json, managed_path) != NULL;
|
||||
bool has_target_path =
|
||||
claude_json && strstr(claude_json, ".local/bin/codebase-memory-mcp") != NULL;
|
||||
|
||||
free(claude_json);
|
||||
cli_activation_restore_env(old_home, old_cache);
|
||||
test_rmdir_r(tmpdir);
|
||||
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_FALSE(target_created);
|
||||
ASSERT_TRUE(has_managed_path);
|
||||
ASSERT_FALSE(has_target_path);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1558: `install` configured EVERY detected client, so a user who wanted
|
||||
* Claude and Codex had to revert OpenCode and Cursor by hand — and the next
|
||||
* install silently recreated them. The selector restricts it.
|
||||
@@ -19090,6 +19149,7 @@ SUITE(cli) {
|
||||
RUN_TEST(cli_ui_config_keys_are_discoverable_and_settable_issue1558);
|
||||
RUN_TEST(cli_skill_frontmatter_scalars_with_colons_are_quoted_issue1554);
|
||||
RUN_TEST(cli_external_manager_detection_needs_positive_evidence_issue1566);
|
||||
RUN_TEST(cli_install_managed_binary_config_path_issue2722);
|
||||
RUN_TEST(cli_clients_selector_vocabulary_is_complete_and_strict_issue1558);
|
||||
RUN_TEST(cli_clients_selector_filters_registry_installs_issue1798);
|
||||
RUN_TEST(cli_update_accepts_retired_variant_flags_issue1544);
|
||||
|
||||
Reference in new issue
Block a user