You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Address codex review on #452:
- install claims a Claude/Codex marketplace only when it was absent before
the install (or a prior receipt already claimed it); pre-existing or
unreadable marketplaces are never recorded as owned
- uninstall scans live host inventory plus every stored receipt for
marketplace dependents and retains the marketplace when any exist or
the inventory is unknown; Claude --purge-data refuses (AB7008) when the
same plugin is installed at another scope or dependents are unknown
- Claude project/local receipts are keyed by project root so parallel
projects no longer overwrite each other's receipt
- doctor inventories store receipts even when the host probe is unavailable
- uninstall --plan for Cursor local simulates the prune precisely;
--keep-data reruns on a remnant are a not-installed no-op
- Cursor marketplace imported detection uses the receipt commit even after
the staged repo is gone
Add `agent-bundle uninstall <host> [--from <bundle-dir>] [--scope <scope>] [--mode local|marketplace] [--keep-data | --purge-data --confirm-purge] [--force] [--plan] [--json]`, the package-relative installer bin's `uninstall <host>`, and the emitted standalone `install.mjs --uninstall`: the receipt-owned reverse of `install`. Uninstall removes exactly the receipt's files and installer-created directories (Cursor local, including the `~/.cursor/plugins[/local]` directories the install created), the staged marketplace repository after its `HEAD` matches the recorded commit (Cursor `--mode marketplace`), or the recorded host registrations (`claude plugin uninstall --keep-data` + `claude plugin marketplace remove`, `codex plugin remove` + `codex plugin marketplace remove`, the marketplace retained while another installed plugin uses it) — and nothing else. Durable runtime state (`state/`) is kept unless `--purge-data --confirm-purge` (`AB7008` without confirmation) with a typed per-host `data.outcome` (`kept`/`purged`/`absent`, Claude `retained-by-host`, Codex `removed-by-host`/`unavailable`); a missing receipt is `AB7006` and an owned-content, version, or `HEAD` mismatch is `AB7007` unless `--force`; foreign directories are refused regardless; `--plan` prints the exact paths and host verbs without changing anything; a rerun is a `not-installed` no-op. Install receipts move to format `agent-bundle-install-receipt/2` as the single lifecycle source of truth (mode, scope, registrations, created host directories, `updatedAt`), Claude/Codex/Cursor-marketplace installs write store receipts under `<host root>/agent-bundle/receipts/`, and format 1 receipts are read with synthesized fields and diagnosed (`AB7327`), never rejected. `agent-bundle doctor --from` reports the lifecycle stage per host (placed → registered → enabled → active, unobservable stages typed `unavailable`; `AB7325`), inventories store receipts and flags orphaned ones (`AB7326`), and explains a Cursor directory holding only preserved runtime state as `missing` (`AB7307`) instead of foreign. Host capability tables gain dated `lifecycle` rows and every adapter revision advances (#452)
`codex plugin remove <id>`, then `plugin marketplace remove <marketplace>`
824
833
and removes the store receipt. Because `plugin marketplace remove` applies
825
-
to every scope, the marketplace is `retained` when another installed plugin
826
-
still names it, when the same plugin is installed at another Claude scope,
827
-
or when the dependency re-read of `plugin list --json` fails (a failed read
828
-
is not proof that nothing depends on it). A registration the host no longer holds is
834
+
to every scope, the marketplace is `retained` when the receipt does not
835
+
record Agent Bundle registering it (it pre-existed the install, or there is
836
+
no receipt), when another installed plugin still names it, when another
837
+
store receipt (another project's scoped install) records it, when the same
838
+
plugin is installed at another Claude scope, or when `plugin marketplace
839
+
list --json` / the dependency re-read of `plugin list --json` cannot be read
840
+
(a failed read is not proof that nothing depends on it). A registration the host no longer holds is
829
841
`already-absent`, so a receipt orphaned behind Agent Bundle's back is
830
842
consumed without running any host verb.
831
843
@@ -839,14 +851,20 @@ for its ~14-day grace period; a purge additionally removes `state/` and
839
851
tree on `plugin remove`), and `unavailable` (Codex has no keep-data option; a
840
852
staged Cursor marketplace holds no runtime state). `--plan` computes the same
841
853
report — exact absolute paths, registrations, data decision — without opening a
842
-
writer, and a second run after a successful uninstall is a `not-installed`
843
-
no-op.
854
+
writer; planned directories are exactly the ones the run would prune (purged
855
+
`state/` first, then every owned directory that would be left empty), never a
856
+
directory kept alive by retained state or unowned entries. A second run after a
857
+
successful uninstall is a `not-installed` no-op. When `--keep-data` left
858
+
`state/` behind under a Cursor local root, the remnant receipt written there
859
+
stays in place (`receipt.status: 'remnant'`) and a rerun without
860
+
`--purge-data` is the same `not-installed` no-op; only `--purge-data
861
+
--confirm-purge` removes the preserved state and prunes the root.
844
862
845
863
| Code | Severity | Trigger | Recovery |
846
864
| --- | --- | --- | --- |
847
865
|`AB7006`| error |`uninstall` found the install but no receipt proving Agent Bundle owns it: a Cursor local copy in the pre-receipt legacy layout, a staged marketplace repository without its store receipt, or a host-registered Claude/Codex copy without its store receipt. | Re-run with `--force` (a legacy Cursor copy is removed by its inventory, `state/` kept; a host-CLI install is removed through the host verbs), or reinstall with `--replace` first to record a receipt. |
848
866
|`AB7007`| error |`uninstall` refused a mismatch or a foreign target: the owned files hash differently from the receipt, the cached host copy differs from the receipt in version or content, the staged repository's `HEAD` is not the recorded commit, the receipt names another plugin, the directory is not this plugin's install at all, or a destination / `state/` entry is a symlink or special file. |`--force` overrides content and `HEAD` mismatches (the receipt-owned set is still the only thing removed); a receipt or manifest naming another plugin, and symlinked entries, are refused regardless — inspect and remove them manually. |
849
-
|`AB7008`| error |`--purge-data` without `--confirm-purge`, or `--purge-data` together with `--keep-data`. | Pass `--purge-data --confirm-purge` to delete durable state, or neither flag to keep it. |
867
+
|`AB7008`| error |`--purge-data` without `--confirm-purge`, `--purge-data` together with `--keep-data`, or (Claude) `--purge-data` while the same plugin is installed at another scope — the cached copy and `plugins/data/<id>/` are scope-less and still in use — or while `claude plugin list --json` cannot be re-read to prove there is no other scope. | Pass `--purge-data --confirm-purge` to delete durable state, or neither flag to keep it; for a shared Claude scope, uninstall without `--purge-data` and purge after the last scope is removed. |
850
868
851
869
The Cursor and portable host-install proofs (`tests/host-install-proof.test.ts`,
852
870
`tests/packed-host-install-proof.test.ts`) snapshot the isolated home before
0 commit comments