From dfd2ab2285f372b0a728af9c4b065d8b9672f43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cochard-Labb=C3=A9?= Date: Wed, 29 Jul 2026 08:00:54 +0200 Subject: [PATCH] feat(install): add CBM_PACKAGE_MANAGED build guard, fix FreeBSD self-path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When built with -DCBM_PACKAGE_MANAGED, `install` no longer mutates the user's $HOME and wires agent configs to the real installed binary: - skips copying the running binary into ~/.local/bin (the package manager already owns it under its prefix), and - skips appending `export PATH=...` to the shell rc, and - points bin_target (hence the MCP `command` written into .claude.json, .mcp.json, etc.) at the running executable instead of the hardcoded ~/.local/bin path. Also add a FreeBSD self-path branch: cbm_detect_self_path() used readlink("/proc/self/exe"), but FreeBSD has no /proc by default, so it silently fell back to ~/.local/bin — writing that wrong path into every agent config. Use sysctl KERN_PROC_PATHNAME, which needs no /proc. The flag is off by default; source installs (scripts/build.sh) are unchanged. Non-FreeBSD self-path detection is unchanged. --- src/cli/cli.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index 5db30c73a..66932dce9 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -94,6 +94,10 @@ static int cbm_powershell_quote_word(const char *value, char *out, size_t out_si #ifdef __APPLE__ #include #endif +#ifdef __FreeBSD__ +#include // KERN_PROC_PATHNAME — /proc-free self-path detection +#include +#endif #include "foundation/compat_fs.h" #ifndef CBM_VERSION @@ -8887,6 +8891,15 @@ static bool cbm_detect_self_path(char *buf, size_t buf_sz, const char *home) { if (!exact) { buf[0] = '\0'; } +#elif defined(__FreeBSD__) + /* FreeBSD has no /proc by default; sysctl KERN_PROC_PATHNAME returns the + * running executable's path without it. */ + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + size_t sp_sz = buf_sz; + exact = sysctl(mib, 4, buf, &sp_sz, NULL, 0) == 0 && sp_sz > 0 && sp_sz <= buf_sz; + if (!exact) { + buf[0] = '\0'; + } #else ssize_t sp_len = readlink("/proc/self/exe", buf, buf_sz - SKIP_ONE); exact = sp_len > 0 && (size_t)sp_len < buf_sz - SKIP_ONE; @@ -9234,12 +9247,28 @@ 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}; - (void)cbm_detect_self_path(self_path, sizeof(self_path), home); + bool self_path_exact = cbm_detect_self_path(self_path, sizeof(self_path), home); + +#ifdef CBM_PACKAGE_MANAGED + /* A package manager (FreeBSD ports, Homebrew, distro pkg) already placed the + * binary on PATH under its own prefix and owns that file. Install must not + * copy it into ~/.local/bin (a duplicate, unmanaged binary in $HOME), and + * agent configs must reference the real binary the manager installed, not + * the ~/.local/bin path. Point bin_target at the running executable. */ + if (self_path_exact && self_path[0]) { + snprintf(bin_target, sizeof(bin_target), "%s", self_path); + } +#else + (void)self_path_exact; +#endif struct stat target_status; bool target_exists = (stat(bin_target, &target_status) == 0); bool same_binary = cbm_same_file(self_path, bin_target); bool do_copy = !same_binary && (!target_exists || force); +#ifdef CBM_PACKAGE_MANAGED + do_copy = false; +#endif /* (#607) Default: preserve existing indexes. `--reset-indexes` opts into * the old prompt-and-delete behaviour. The helper returns 0 only when the @@ -9400,7 +9429,10 @@ int cbm_cmd_install(int argc, char **argv) { } } char shell_rc[CLI_BUF_1K] = {0}; -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(CBM_PACKAGE_MANAGED) + /* Leave shell_rc empty under CBM_PACKAGE_MANAGED: the package manager owns + * PATH via its prefix, so install must not append `export PATH=...` to the + * user's shell rc. cli_install_activate() skips the PATH step on empty rc. */ snprintf(shell_rc, sizeof(shell_rc), "%s", cbm_detect_shell_rc(home)); #endif cli_install_activation_t activation = { @@ -9454,7 +9486,9 @@ int cbm_cmd_install(int argc, char **argv) { printf("\nInstall complete. Please restart your coding-agent sessions to " "properly take this into account.\n"); #ifndef _WIN32 - printf("Restart your shell or run:\n source %s\n", shell_rc); + if (shell_rc[0]) { + printf("Restart your shell or run:\n source %s\n", shell_rc); + } #endif if (dry_run) { printf("\n(dry-run — no files were modified)\n");