Skip to content

uninstall: one symlinked agent config aborts the whole run — executable and indexes (386 MB) left behind #1954

Closed

Description

@Jmeg8r

Version

0.10.8

Platform

macOS (Apple Silicon)

Install channel

GitHub release archive / install.sh / install.ps1

Binary variant

standard

What happened, and what did you expect?

Summary

uninstall aborts entirely when any one agent config path is a file
symlink. A single unwritable Cursor path (~/.cursor/mcp.json, symlinked into
a dotfiles repo) stopped the run before executable and index removal started,
leaving 386 MB on disk after a reported "uninstall".

The refusal to write through a symlink looks deliberate and defensible. The
problem is the blast radius: a Cursor-specific config check gates teardown
steps that have nothing to do with Cursor.

Observed

Found 2 index(es):
  /Users/<me>/.cache/codebase-memory-mcp/hr-moderate.db
  /Users/<me>/.cache/codebase-memory-mcp/_config.db
Delete these indexes? (y/n): y (auto)
Stopping active CBM sessions and operations for uninstall...
error: agent_config agent=Cursor op=mcp_uninstall path=/Users/<me>/.cursor/mcp.json (target: symlink, 43 bytes)
error: one or more agent cleanup operations failed; executable and index removal were not started
error: activation stopped after one or more agent configuration or cleanup operations failed; the published/current executable was kept, and configuration changes that completed may remain. Please restart your coding-agent sessions after resolving the errors above.

~/.cursor/mcp.json is lrwxr-xr-x ... -> ~/.dotfiles/cursor/mcp.json — a
normal dotfiles arrangement, present long before CBM was installed.

Left behind

item size
~/.local/bin/codebase-memory-mcp 282 MB
~/.cache/codebase-memory-mcp/ 104 MB
~/.local/bin/install.sh (updater) —
~/.zshrc PATH line —

All four had to be removed by hand. Note this install used --skip-config, so
there was no CBM entry in the Cursor file at all — the run aborted over a file
it had never written to and had nothing to remove from.

Expected

Teardown of CBM-owned artifacts (executable, indexes, updater) should not be
gated on agent-config cleanup. Suggested, in rough priority order:

  1. Decouple. Remove the executable and indexes even when some agent config
    cleanup fails, then report the skipped configs and exit non-zero. The user
    can fix a symlinked config later; they cannot easily discover that 386 MB
    survived a successful-looking uninstall.
  2. Downgrade this case to a warning. A path with no CBM entry to remove is
    a no-op, not a failure — especially under --skip-config.
  3. Resolve and validate the link target. If it resolves inside the user's
    home and is a regular file, editing it is equivalent to editing the real
    path. Refusing only links that escape the home directory would keep the
    security property while accepting ordinary dotfiles setups.

Secondary: the report describes actions it did not take

Both --dry-run and the real run print per-client past-tense lines regardless
of whether anything exists:

  removed MCP config entry
  removed PreToolUse + SessionStart + SubagentStart hooks
Codex CLI: removed MCP config entry
  removed instructions
  Continue / cn:
  mcp: removed canonical entry from /Users/<me>/.continue/config.yaml

None of that happened. Verified by mtime — ~/.continue/config.yaml was last
modified 2026-05-19 and ~/.cursor/mcp.json 2026-05-24, both months before the
install; ~/.qoder/ and ~/.codebuddy/ do not exist on this machine. A
find across ~/.claude ~/.codex ~/.gemini ~/.cursor ~/.config for
*codebase-memory* returned zero results both before and after.

--dry-run additionally mixes tenses, printing bare removed ... for some
clients and would remove owned profile <path> for others in the same output,
which reads as "these files exist and will be deleted" when they do not.

Reading either output alone, an operator would reasonably conclude a dozen
configs had been rewritten.

Related

Reproduction

Public repo, macOS 15 (Darwin 25.5.0), arm64, zsh.

Runs in a throwaway HOME so it cannot touch a real Cursor config — the
ln -sf below would otherwise clobber an existing ~/.cursor/mcp.json:

export HOME=$(mktemp -d)   # isolated; nothing below touches your real home
cd "$HOME"

# 1. dotfiles-style symlinked Cursor config (the trigger)
mkdir -p "$HOME/.dotfiles/cursor" "$HOME/.cursor"
echo '{"mcpServers":{}}' > "$HOME/.dotfiles/cursor/mcp.json"
ln -s "$HOME/.dotfiles/cursor/mcp.json" "$HOME/.cursor/mcp.json"

# 2. install without touching agent config
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash -s -- --skip-config
export PATH="$HOME/.local/bin:$PATH"

# 3. index any public repo so an index exists
git clone --depth 1 https://github.com/chopratejas/headroom.git "$HOME/hr"
codebase-memory-mcp cli index_repository --repo-path "$HOME/hr" --mode moderate --name hr

# 4. uninstall — aborts
codebase-memory-mcp uninstall -y

# 5. artifacts survive the "uninstall"
ls -lh "$HOME/.local/bin/codebase-memory-mcp"
du -sh "$HOME/.cache/codebase-memory-mcp"

Step 5 shows the 282 MB executable and the index cache still present.

Discard the sandbox with rm -rf "$HOME" while it is still the temp dir, then
open a fresh shell to restore your real HOME.

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Activity

  1. CaptainMittens commented on Sep 3, 2026

    @CaptainMittens
    Contributor

    There is a second trigger for this, and it needs no symlink: the agent config file simply
    does not exist. Your closing line — "the run aborted over a file it had never written to
    and had nothing to remove from" — turns out to be the general case, not a side effect of
    the symlink.

    It also reproduces inside this repository's own test suite, on current main (c38aa35c),
    so it does not need a dotfiles setup to see.

    Reproduction

    cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index
    (tests/test_cli.c:1700) fails on any machine that has the goose binary on PATH. The
    test redirects HOME and CBM_CACHE_DIR to a temporary directory but does not redirect
    PATH, so Goose is detected from the real machine while the fixture has no
    .config/goose/config.yaml for it.

    Same tree, same built test-runner, only PATH differs:

    Run Result
    goose on PATH 291 passed, 1 failed
    goose removed from PATH 292 passed, 0 failed

    The failing run prints exactly the pair of lines from your report:

    error: agent_config agent=Goose op=mcp_uninstall path=/tmp/cli-daemon-uninstall-7gY0EY/.config/goose/config.yaml (target: does not exist or cannot be inspected)
    error: one or more agent cleanup operations failed; executable and index removal were not started
    

    The test then fails on ASSERT_EQ(rc, 0) with rc == 1, and asserts that the binary and
    the index were left in place — which is your bug, observed as a test failure.

    The path it takes

    1. cbm_remove_goose_mcp_owned (src/cli/cli.c:3930) calls cbm_remove_yaml_stdio_mcp
      (:3907), which calls cbm_yaml_remove_owned_mapping_entry
      (src/cli/config_yaml_edit.c:3627). That returns CBM_YAML_IDENTITY_EDIT_ERROR when it
      cannot take a lock on the path. I did not confirm which step fails first for an absent
      file, but the observed message confirms the path is not there and the return is negative.
    2. uninstall_agent_mcp_instr (src/cli/cli.c:10745-10758) treats any
      remove_result < CLI_OK as an error and calls record_agent_config_error at :10753.
      There is no case for "nothing to remove".
    3. g_agent_uninstall_errors is then non-zero, and cli_uninstall_activate
      (src/cli/cli.c:11860-11866) aborts before the executable and index removal, exactly as
      you saw.

    So the blast radius you describe is not Cursor-specific. Any detected agent whose config is
    absent, or unwritable for any reason, stops teardown for every other agent and for the
    282 MB binary.

    One more place worth checking in the same fix

    The registry-profile loop has the same shape at src/cli/cli.c:11106-11109:

    if (edit_result == CBM_AGENT_EDIT_FOREIGN) {
        ...
    } else if (edit_result != CBM_AGENT_EDIT_OK) {
        record_agent_config_error(true, profile->display_name, "mcp_uninstall", config_path);
    }

    cbm_agent_edit_result has four values (src/cli/agent_clients.h:87-90), and
    CBM_AGENT_EDIT_NOT_APPLICABLE means there was nothing to edit. It falls into the error
    branch here. I have not seen that one fire in practice, so I raise it as something to check
    rather than as a second confirmed defect.

    Happy to send a PR if you want one, and to say which shape you prefer: treat "absent or
    nothing to remove" as success at the two call sites, or keep it an error and stop it from
    gating the executable and index removal. They are different decisions and I would rather
    you picked.

  2. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    ux/behaviorDisplay bugs, docs, adoption UX
    on Sep 5, 2026
  3. DeusData commented on Sep 7, 2026

    @DeusData
    Owner

    Thank you for the precise report and the isolated-HOME reproduction — it made this straightforward to verify. Two things were tangled here.

    The absent-config half (an agent detected only by its binary on PATH, with no config file in HOME, e.g. Hermes or Goose) is fixed on main by 3007dbf: the YAML removers now answer OK for a genuinely absent path, so uninstall no longer refuses on it.

    The half you hit — a config file that is a symlink — is still open on main; we reproduced it exactly (~/.cursor/mcp.json -> dotfiles, uninstall -y → rc 1, (target: symlink, 45 bytes), binary and cache left behind). The JSON editors open with O_NOFOLLOW on purpose, and that refusal still gates executable and index removal. The direction is settled: symlinks owned by the invoking user will be followed (resolving to the target, so your dotfiles link stays a link), and executable/index removal will no longer be held hostage by a single agent-config failure. Your secondary point stands too: the per-client removed … lines are printed regardless of whether anything existed, including in --dry-run, and the report will say what actually happened. Fixing commits get linked here when they land; target is v0.10.9.

  4. added 2 commits that reference this issue on Sep 9, 2026
  5. DeusData commented on Sep 12, 2026

    @DeusData
    Owner

    Fixed on main by #2110 (merge commit b790be3d15d44f0d4629a2c97ddf76c104d80d22).

    Uninstall no longer aborts the whole run when a single agent-config entry is a symlink: a user-owned symlinked config is now followed rather than treated as a hard failure, and an agent-config failure no longer aborts the uninstall. Ships in v0.10.9.

    Thanks for the report — the "one symlink aborts everything" failure mode was exactly the sharp edge this removes.

  6. sanathusk commented on Oct 17, 2026

    @sanathusk

    A similar occurs during install if any of the harness config is a symbolic link, say settings.json from Zed is a symbolic link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UXwindowsWindows-specific issues

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions