Repository navigation
uninstall: one symlinked agent config aborts the whole run — executable and indexes (386 MB) left behind #1954
Description
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:
- 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. - 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. - 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
- install fails all Claude Code agent/hook writes when ~/.claude is a directory symlink — misleading "(target: does not exist or cannot be inspected)" #1722 — same underlying symlink refusal on the install path (Windows,
directory symlink, different message:target: does not exist or cannot be inspected). This report is the uninstall path on macOS with a file
symlink and an explicittarget: symlinkdetection; the distinguishing harm
is the abort cascade into executable/index removal. uninstall --helpperforms a real uninstall — unknown flags silently ignored, no confirmation before destructive removal #1038 — also concernsuninstallsafety/consistency.
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
CaptainMittens commentedon Sep 3, 2026
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 thegoosebinary onPATH. Thetest redirects
HOMEandCBM_CACHE_DIRto a temporary directory but does not redirectPATH, so Goose is detected from the real machine while the fixture has no.config/goose/config.yamlfor it.Same tree, same built
test-runner, onlyPATHdiffers:gooseonPATH291 passed, 1 failedgooseremoved fromPATH292 passed, 0 failedThe failing run prints exactly the pair of lines from your report:
The test then fails on
ASSERT_EQ(rc, 0)withrc == 1, and asserts that the binary andthe index were left in place — which is your bug, observed as a test failure.
The path it takes
cbm_remove_goose_mcp_owned(src/cli/cli.c:3930) callscbm_remove_yaml_stdio_mcp(
:3907), which callscbm_yaml_remove_owned_mapping_entry(
src/cli/config_yaml_edit.c:3627). That returnsCBM_YAML_IDENTITY_EDIT_ERRORwhen itcannot 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.
uninstall_agent_mcp_instr(src/cli/cli.c:10745-10758) treats anyremove_result < CLI_OKas an error and callsrecord_agent_config_errorat:10753.There is no case for "nothing to remove".
g_agent_uninstall_errorsis then non-zero, andcli_uninstall_activate(
src/cli/cli.c:11860-11866) aborts before the executable and index removal, exactly asyou 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:cbm_agent_edit_resulthas four values (src/cli/agent_clients.h:87-90), andCBM_AGENT_EDIT_NOT_APPLICABLEmeans there was nothing to edit. It falls into the errorbranch 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.
test(cli): point PATH at the fixture so uninstall isolation holds on …
DeusData commentedon Sep 7, 2026
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-clientremoved …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.fix(cli): keep config follow-roots off the static TLS block (#2110)
Merge pull request #2110 from DeusData/fix/1954-uninstall-symlinked-c…
DeusData commentedon Sep 12, 2026
Fixed on
mainby #2110 (merge commitb790be3d15d44f0d4629a2c97ddf76c104d80d22).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.
fix(cli): uninstall no longer gated by agent-config cleanup failures
fix(cli): follow user-owned symlinked config files in the editors
test(cli): point PATH at the fixture so uninstall isolation holds on …
fix(cli): uninstall no longer gated by agent-config cleanup failures
fix(cli): follow user-owned symlinked config files in the editors
fix(cli): keep config follow-roots off the static TLS block (DeusData…
fix(cli): uninstall no longer gated by agent-config cleanup failures
fix(cli): follow user-owned symlinked config files in the editors
fix(cli): keep config follow-roots off the static TLS block (DeusData…
sanathusk commentedon Oct 17, 2026
A similar occurs during install if any of the harness config is a symbolic link, say settings.json from Zed is a symbolic link