Skip to content

fix(cli): activation leaves no permanent drift - #773

Merged
blafourcade merged 1 commit into
nextfrom
fix/activation-does-not-leave-permanent-drift
Sep 4, 2026
Merged

blafourcade merged 1 commit into
nextfrom
fix/activation-does-not-leave-permanent-drift

Conversation

@blafourcade

@blafourcade blafourcade commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

What

MarketplaceSyncSettingsUseCase.execute ran in this order:

  1. syncTool writes .claude/settings.json and hashes what it wrote into the manifest.
  2. manifestRepo.save(manifest).
  3. activateNativeTools shells out to claude plugin marketplace add and claude plugin enable.

Step 3 writes into the same file step 1 hashed. Claude Code declares settingsPath: ".claude/settings.json" and no enabledPluginsSettingsPath, so both halves land there — and the file is hash-tracked precisely because the two paths coincide (syncEnabledPluginsFile's own if (settings.enabledPluginsSettingsPath == null)).

Nothing re-read it. The tracked hash described content that stopped existing the moment activation succeeded, so:

  • status and doctor reported a file the person never touched as drifted, for as long as the manifest stood.
  • restore would have undone the host's own registration to reach a state AIDD held for the length of one function.

The wrong answer that came first

The first probe passed — and was vacuous. The fake activator wrote enabledPlugins: { <ref>: true }, which is exactly the key mergeEnabledPlugins had already written, so the content was byte-identical and the hash matched by coincidence. Replacing it with a key this code never writes — the host's own bookkeeping — turned the probe red.

Change

After activation the settings file is re-read and what is there is stored, for the tools whose own CLI actually ran. activateTool now answers whether it drove the CLI.

Only those tools. A tool whose binary is absent wrote nothing, so a settings file that differs from its tracked hash differs because a person changed it — the drift status exists to report and restore exists to undo. Re-hashing every installed tool would bless that as ours and make the person's change permanent and invisible.

Re-read rather than re-derived: what is stored is what is on disk after the write — an observation, not a guess at what the host would have written.

Guards

Guard Mutation that killed it
the tracked hash matches the file after the host CLI has written to it drop the re-hash, i.e. the code as it was — 1
a hash is left alone for a tool whose own CLI never ran re-hash every installed tool — 1

The second is what keeps the first honest: without it the fix is indistinguishable from "always trust the file".

Windows caught the first push

Both new tests failed on cli / Windows at 17d64ee7 — a real failure, not the reporter flake #766 fixed. They built the settings key as a /-joined literal while the production code uses resolve, so on Windows the test addressed \\test-project\\.claude\\settings.json under a name the use case never wrote. Now resolved the same way the code does, with the reason stated where the helper lives.

Worth recording: the job that caught it is the one #766 existed to make trustworthy, and this is the first time since that it has failed for a reason that was actually mine.

Gates: 3454 tests / 308 files, tsc, biome ci (2 pre-existing warnings, 0 errors), knip, jscpd, bundle within budget, 369 repository script tests, 0 broken links, cli layering clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

@blafourcade
blafourcade requested a review from a team as a code owner September 4, 2026 23:24
`MarketplaceSyncSettingsUseCase` wrote `.claude/settings.json`, hashed what it
wrote, saved the manifest, and only then shelled out to `claude plugin
marketplace add` and `claude plugin enable` — which write into that same file.
Claude Code declares no separate `enabledPluginsSettingsPath`, so both halves
land there, and the file is hash-tracked precisely because the two paths
coincide.

Nothing re-read it. The tracked hash described content that stopped existing the
moment activation succeeded: `status` and `doctor` reported a file the person
never touched as drifted for as long as the manifest stood, and `restore` would
have undone the host's own registration to reach a state AIDD held for the
length of one function.

The first probe of this passed, and was vacuous — the fake activator wrote the
same key `mergeEnabledPlugins` already writes, so the content was byte-identical
and the hash matched by coincidence. Writing a key this code never writes, which
is what the host's own bookkeeping is, turned it red.

After activation the settings file is re-read and what is there is stored, for
the tools whose own CLI actually ran. `activateTool` now answers whether it drove
the CLI. Only those tools: a tool whose binary is absent wrote nothing, so a file
that differs for it differs because a person changed it — the drift `status`
exists to report — and re-hashing every installed tool would bless that as ours
and make the change permanent and invisible.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
@blafourcade
blafourcade force-pushed the fix/activation-does-not-leave-permanent-drift branch from 17d64ee to 5d76a5c Compare September 4, 2026 23:32
@blafourcade
blafourcade merged commit 462431a into next Sep 4, 2026
17 checks passed
@blafourcade
blafourcade deleted the fix/activation-does-not-leave-permanent-drift branch September 6, 2026 06:22
@aidd-bot aidd-bot Bot mentioned this pull request Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant