Skip to content

fix(install): Codex add-only replace; refuse disabled/unknown enablement - #824

Merged
ScriptedAlchemy merged 5 commits into
mainfrom
cursor/codex-install-add-only-71f7
Sep 17, 2026
Merged

ScriptedAlchemy merged 5 commits into
mainfrom
cursor/codex-install-add-only-71f7

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Codex replacement currently runs plugin remove before plugin add. Remove deletes the plugin's settings subtree, so an enabled plugin with a disabled nested MCP server loses that override during an upgrade.

Replacement now uses native plugin add without removal, preserving nested settings and refreshing the cached artifact. Disabled or unknown-enablement replacements refuse before mutation (AB7004), since native add enables the plugin and the plugin CLI offers no qualified way to preserve its disabled state. Failed add/receipt recovery also avoids removing an existing Codex plugin. Claude, fresh installs and explicit uninstall retain their existing behavior.

The installer does not write or restore config.toml. Native inventory is a snapshot: a concurrent edit to this same plugin's enabled flag between list and add is not atomically protected. Unrelated configuration is never rewound.

Validation: independent exact-head review PASS+NOTES at 2ebfcd87ba803a4a6b2f80288ebee809d4ae41e8; isolated actual Codex 0.154 add-only refresh/removal-of-obsolete-files/nested-override proof; enabled/disabled/unknown and add/receipt-failure regression tests; native CI, host-install and Ubuntu/macOS/Windows filesystem checks, build/types/release gates and EN/ZH documentation checks. A patch changeset is included.

Native `codex plugin remove` deletes the plugin config subtree, so
`agent-bundle install codex` now refreshes through add-only. Capture
config.toml around add and restore it when inventory reported
enabled=false or when add fails after mutation.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2ebfcd8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agent-bundle Patch
create-agent-bundle Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…tings

The reinstall recipe is add-only; the prose still names plugin remove so
operators know why that verb is not in the executable block.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

HOLD merge (owner note)

Merge is held at 269f875 pending independent review + revised implementation/proof.

Parent constraints (must land before merge):

  1. No whole config.toml capture/restore — that can overwrite unrelated concurrent user/native edits and violates the preservation boundary.
  2. Fail closed before mutation when existing disabled state cannot be safely preserved (missing/unreadable prior config must not re-enable a disabled plugin).
  3. Native add-only remains the proved/correct base for nested overrides / version / drift.
  4. Restore only the exact prior plugin setting that native add changes; prefer a supported native settings API with expected-version compare if available.
  5. Do not overwrite whole user config, add a TOML parser/framework, or invent permissions.

Cloud worker owns the surgical revise + PR body rewrite around the corrected final approach. Independent review + proof required before any merge.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Independent review: FAIL @ 269f875 — merge still held

Blocking:

  1. Whole-config restoration can overwrite unrelated concurrent edits.
  2. Treating all read errors as absent can re-enable a disabled plugin.
  3. Direct writeFile truncates live config and may leave it partial on failure.

Required direction (owner → cloud worker):

  • Keep native add-only.
  • Discovered regression = enabled replacements with existing nested overrides → must work natively without remove.
  • Restore/correct only the prior plugin enabled flag via a qualified scoped native write with conflict detection; preserve unrelated config + native nested tables.
  • If pinned host lacks a qualified settings-preserving update API: fail closed before mutation for disabled/unknown-enablement replacement; document that supported boundary; leave those installs unchanged.
  • No general TOML parser / new client framework to override user intent.
  • Fresh install, Claude, explicit uninstall unchanged.
  • Tests: concurrent unrelated edits; config read/write failure; failed-add/receipt recovery.

Merge only after a revised exact head passes independent review. PR body must match final behavior.

@pkg-pr-new

pkg-pr-new Bot commented Sep 17, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@824
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@824
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/rsc-markdown-stream@824
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@824

commit: 2ebfcd8

Keep native add-only for enabled Codex replacements so nested MCP
overrides survive. Pinned Codex has no settings-preserving update API,
so disabled or unknown-enablement replacements fail closed before any
host mutation instead of capturing or rewriting config.toml.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@cursor cursor Bot changed the title fix(install): Codex replace is add-only so plugin settings survive fix(install): Codex add-only replace; refuse disabled/unknown enablement Sep 17, 2026
cursoragent and others added 2 commits September 17, 2026 04:02
State that the native plugin CLI has no qualified settings-preserving
update API. Do not deny app-server config/value/write. Record that the
plugin-list enabled snapshot is not atomic against a concurrent edit of
the same plugin flag between list and add.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Align install and INSTALL.md assertions with the production
AB7004 / Codex reinstall phrase.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@ScriptedAlchemy
ScriptedAlchemy marked this pull request as ready for review September 17, 2026 04:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-17T04:19:27.110608Z 2ebfcd8 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ebfcd87ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Decided before any host verb runs, so the marketplace ownership check sees the pre-install state.
const recorded = await receiptIdentity();
if (replaced) {
const replaceRemovesPlugin = replaced && host !== 'codex';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve clean replacements on pinned Codex 0.147

When a supported Codex 0.147.0 user replaces a bundle that removed or renamed a file, this unconditional exclusion skips the only step that cleans the cache. The pre-change native instructions explicitly recorded that plugin add re-copies the snapshot but never deletes files removed by a rebuild, while the repository still pins and supports 0.147.0; the commit's stated real-host proof covers only 0.154. Installation can therefore report replaced and write the new receipt while obsolete skills or hooks remain, and the manifest-based Doctor comparison can ignore files no longer declared. Gate add-only replacement on a proven host version, raise the supported version and pin, or provide another settings-preserving clean replacement.

Useful? React with 👍 / 👎.

@ScriptedAlchemy
ScriptedAlchemy merged commit ab5ae66 into main Sep 17, 2026
20 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 17, 2026
ScriptedAlchemy added a commit that referenced this pull request Sep 17, 2026
After rebasing onto main, the Codex add-only install tests still used bare
recursive rm in finally blocks; the remove-tree lint gate correctly failed.
ScriptedAlchemy added a commit that referenced this pull request Sep 17, 2026
#823)

* test(support): add removeTree, a retrying recursive delete for test teardown

A late writer into a tree being removed makes rm reject with ENOTEMPTY. One helper retries the transient codes so each call site does not carry its own maxRetries.

* test: replace bare recursive rm in tests with removeTree and gate it in lint

scripts/check-test-remove-tree.mjs fails pnpm lint on rm(..., { recursive: true }) without maxRetries under packages/*/tests. 1156 call sites across 204 files moved to removeTree.

* ci: retrigger checks after unreproducible removeTree lint failure

* fix(test): convert leftover recursive teardowns and catch aliased rm

After merging main, three install-surface teardowns still used bare
recursive rm. Convert them to removeTree, and teach the lint gate to
recognize namespace and aliased Node fs removal bindings so those forms
cannot escape the gate.

* fix(test): make remove-tree lint syntax-aware for comments and $ aliases

Mask comments/strings before scanning, escape imported names literally, and
read recursive/maxRetries from real options properties so GPT P2 cases pass.

* test(support): remove the tmp dir the persistent-ENOTEMPTY test leaves behind

* test: declare scripts/check-test-remove-tree.mjs for the gate test's typecheck

* fix(test): parse remove-tree lint calls with typescript-5

Replace comment/string masking with an AST walk so options come only
from the second argument (including quoted keys), and regex/template
edge cases no longer hide or invent recursive rm hits.

* fix(test): parse Node fs removal bindings via TypeScript AST

Stop forging bare `rm` bindings from text regex so local identifiers and
commented-out imports cannot bypass or falsely trip the removeTree lint gate.

* test(remove-tree): cover AST binding regressions for GPT P2

Lock commented bare imports, non-fs rm imports, and aliased/namespace
maxRetries pass paths so removalBindings stays ImportDeclaration-backed.

* fix(test): route #824 install teardown through removeTree

After rebasing onto main, the Codex add-only install tests still used bare
recursive rm in finally blocks; the remove-tree lint gate correctly failed.

* ci: retrigger checks after cancelled CI attempts on removeTree PR
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.

2 participants