Skip to content

fix(codex): fix marketplace manifest, registration ordering, and EPERM guard#56

Merged
chenliuyun merged 21 commits into
mainfrom
fix/codex-marketplace-manifest
May 25, 2026
Merged

fix(codex): fix marketplace manifest, registration ordering, and EPERM guard#56
chenliuyun merged 21 commits into
mainfrom
fix/codex-marketplace-manifest

Conversation

@chenliuyun

Copy link
Copy Markdown
Collaborator

Summary

  • Fix Codex marketplace manifest path and format (.claude-plugin/marketplace.json) so Codex CLI correctly recognizes the plugin
  • Fix marketplace registration: correct operation ordering (clear stale DB before re-registering), fix plugin path resolution, add root-level marketplace.json for git sparse checkout
  • Refactor @switchbot/openclaw-skill to delegation model — bootstraps CLI/auth then hands off to switchbot mcp serve
  • Add EPERM guard to resolveMarketplaceSourceRoot symlink/junction creation in both src/install/codex-checks.ts and packages/codex-plugin/bin/install.js, with tests covering all three call-site branches
  • Gate releases on plugin registration smoke tests; expand pre-commit verification

Test Plan

  • npm test — all tests pass (2,769 tests)
  • npm run verify:release-gate — build + unit + smoke tests pass (runs on pre-push hook)
  • Codex plugin installs correctly from marketplace on Windows (including @-scoped npm paths)
  • switchbot codex repair succeeds after stale DB state
  • @switchbot/openclaw-skill bootstraps and delegates to switchbot mcp serve

chenliuyun and others added 21 commits May 25, 2026 17:25
…e the plugin

codex plugin marketplace add looks for a manifest at the ROOT of the path
provided. The manifest was buried in .agents/plugins/, causing the
marketplace root does not contain a supported manifest error on every new
install via both Route A (local npm junction) and Route B (git sparse checkout).

Changes:
- packages/codex-plugin/marketplace.json: new root-level manifest, name=switchbot, source.path=./
- .agents/plugins/marketplace.json: rename from codex-plugin to switchbot (both agree)
- package.json: add marketplace.json to files array so it ships in the npm package
- CODEX_PLUGIN_DEFAULT_ID: switchbot@codex-plugin -> switchbot@switchbot
- CODEX_PLUGIN_LEGACY_IDS: add switchbot@codex-plugin for backward-compat cleanup
- resolveMarketplaceName / resolvePluginIdentifier: check root marketplace.json first
- plugin remove loops: deduplicate IDs with Set to avoid double-removing on fallback paths
- setup / repair output: add 3-state status block (CLI / Credentials / Codex plugin)
  and clear Restart Codex Desktop instruction on success

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.gitattributes sets text=auto eol=lf for the whole repo so Git never
converts LF->CRLF on Windows checkout. This eliminates the phantom
"modified" entries on auth.js / policy-edit.js / start.js that appeared
whenever core.autocrlf=true.

package-lock.json was missing the 3.7.3->3.7.4 and codex-plugin
0.1.0->0.1.2 version bumps that should have been part of the v3.7.4
release commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lt ID

CODEX_PLUGIN_DEFAULT_ID changed from switchbot@codex-plugin to
switchbot@switchbot. The legacy IDs array now has 2 entries, so the
deduped plugin-remove loop emits 3 calls on Route B (vs 2 before).

Four tests updated:
- runCodexPluginRegistration x2: add 3rd remove mock
- registerCodexPluginAuto routes-git: add 3rd remove mock
- stepRegisterCodexPlugin success: add 3rd remove mock + expect switchbot@switchbot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ests

Previously pre-commit only ran build + tests/version.test.ts (~2 s).
That meant breaking changes to codex-checks / codex.ts / codex-plugin
would only surface in CI — never locally before the push.

New verify:pre-commit:
  npm run build
  && npm test -- tests/version.test.ts
                 tests/install/codex-checks.test.ts
                 tests/commands/codex.test.ts
  && npm run test:workspaces

Coverage now locked at pre-commit:
  - codex-checks.test.ts  : resolvePluginId, Route A/B/auto, stepRegister
  - codex.test.ts          : setup / repair step outputs
  - test:workspaces        : packages/codex-plugin (install, auth, resolve)

Total timing ~8 s (was ~2 s). Acceptable for the safety gain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ale DB, fix plugin path

Three bugs fixed:

1. **Op ordering**: plugin removes must happen BEFORE marketplace add.
   When plugin remove deletes the last plugin in a marketplace, Codex
   auto-deletes the marketplace directory. Old code did marketplace add
   first, then plugin remove — which destroyed the just-added marketplace.

2. **Stale DB**: add marketplace remove before marketplace add.
   After Codex deletes the marketplace directory it leaves a stale DB
   record. Subsequent marketplace add says "already added" without
   recreating the directory, so plugin add fails. Fix: run
   `codex plugin marketplace remove` for all known names first.

3. **Plugin path in marketplace.json**: `path: "./"` (root) is not
   discovered by Codex 0.133.0. Move plugin files to plugins/switchbot/
   and point marketplace.json there. Also fixes Route B (git sparse
   checkout) which needs the plugin in a proper subdirectory to be
   listed by `codex plugin list`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codex sparse checkout (`--sparse packages/codex-plugin`) clones the full
repo structure with the repository root as the marketplace root. Placing
marketplace.json at packages/codex-plugin/ puts it at the wrong level —
Codex looks for the manifest at the clone root, not the sparse path.

Adding a root-level marketplace.json that points the plugin path to
./packages/codex-plugin/plugins/switchbot fixes Route B registration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cation and format

Codex CLI looks for marketplace manifests at two locations:
  .agents/plugins/marketplace.json (old format)
  .claude-plugin/marketplace.json  (new format, matching claude-plugins-official)

The old .agents/plugins/marketplace.json had path '../../' which resolves
to two levels above the marketplace root, causing Codex to silently skip
the entire marketplace in plugin list and plugin add.

Changes:
- Add .claude-plugin/marketplace.json at repo root (Route B, git sparse checkout)
  with path ./packages/codex-plugin/plugins/switchbot
  Also add --sparse .claude-plugin to the git marketplace add command
- Add packages/codex-plugin/.claude-plugin/marketplace.json (Route A, local npm)
  with path ./plugins/switchbot
- Remove packages/codex-plugin/.agents/plugins/marketplace.json (wrong path)
- Remove root marketplace.json (Codex does not check that location)
- Add packages/codex-plugin/plugins/switchbot/.mcp.json (was referenced by
  plugin.json mcpServers field but missing from the directory)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r-32 retry

- packages/codex-plugin/package.json: bump to 0.1.3, replace .agents/ with
  .claude-plugin/ and plugins/ in files array so Codex finds the manifest
- codex-checks.ts resolveMarketplaceName: check .claude-plugin/marketplace.json
  first (canonical for >=0.1.3), then marketplace.json, then .agents/ legacy
- install.js resolvePluginIdentifier: same priority order as above
- runCodexPluginRegistrationGit: increase os-error-32 retry wait 3 s -> 10 s
  to give Windows Defender time to finish scanning the cloned files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Run the same Codex release gate before push, CI, and publish so marketplace
registration regressions fail before users hit setup. Add Route A and Route B
smokes plus package-level install assertions to cover fresh plugin registration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-add openclaw-skill to the monorepo publish/smoke workflows and bump it to 0.1.1. Align the package metadata, README, and JS entrypoint with the actual runtime contract where OpenClaw bootstraps via bin/start.js and delegates to switchbot mcp serve.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lugin fallback

Replace hardcoded 'switchbot@codex-plugin' with CODEX_PLUGIN_DEFAULT_ID
constant in the repairStepRemovePlugin function. This ensures the repair
step correctly removes the currently-installed 'switchbot@switchbot' plugin
when resolveCodexPackageRoot() fails, now that the constant was updated
to reflect the new plugin ID.

Also update the test to verify the correct constant is used and to expect
all three plugin IDs (default + 2 legacy) to be removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When HEAD is detached (e.g., tag-triggered CI releases), 'git rev-parse --abbrev-ref HEAD' returns 'HEAD', causing 'git clone --branch HEAD' to fail.

Solution: detect detached HEAD via --abbrev-ref returning 'HEAD', then:
1. Use full commit SHA from 'git rev-parse HEAD'
2. Clone without --branch (since git clone --branch requires named refs)
3. Let existing checkout step handle both branch names and SHAs

Fixes smoke test crashes on tag-based releases and detached HEAD checkouts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g for git ref detection

- Change regex from /^[0-9a-f]{40}$/ to /^[0-9a-fA-F]{40}$/ to accept uppercase hex in CODEX_GIT_MARKETPLACE_REF
- Wrap ref detection block in try/catch to provide informative error if git rev-parse fails

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hardcoded '0.1.3' version check with dynamic read from
packages/codex-plugin/package.json, ensuring the test doesn't break
when the plugin version is bumped. Aligns with the pattern used in
smoke-codex-pack-install.mjs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lugin/marketplace.json

Replace the if/else chain in resolvePluginIdentifier with sequential independent
if blocks in a for loop. This allows fallback paths to work even if earlier
manifest files exist but contain invalid JSON (e.g., interrupted write).

Mirrors the correct pattern already used in src/install/codex-checks.ts
(resolveMarketplaceName function).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add index-based ordering assertions to ensure all `codex plugin remove` calls happen BEFORE `codex plugin marketplace add`, not after. The previous check using Array.includes only verified presence, not order.

This enforces the critical invariant introduced in this PR: stale plugins must be removed before the marketplace source is added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add test case to ensure that runCodexPluginRegistrationGit passes both
--sparse flags (packages/codex-plugin and .claude-plugin) to the
marketplace add command. Previously only return values were tested.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chenliuyun chenliuyun merged commit a59e086 into main May 25, 2026
15 checks passed
@chenliuyun chenliuyun deleted the fix/codex-marketplace-manifest branch May 25, 2026 14:16
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