codex plugin list reports installed, enabled for a plugin whose plugins/cache payload is missing, and the model sees none of its skills

Open 💬 2 comments Opened Jul 20, 2026 by konard

Summary

codex plugin list reports a plugin as installed, enabled (and --json
returns "installed": true, "enabled": true) when the plugin's payload under
$CODEX_HOME/plugins/cache/<marketplace>/<plugin>/<version>/ has lost its
skills/ subtree. In that state the plugin contributes zero skills to
<skills_instructions>, so a model is told nothing about skills the operator
believes are installed, and any automation gated on plugin list skips the one
command that would fix it.

Enablement (config.toml) and exposure (the materialized payload) are tracked
independently and never reconciled, and there is no codex plugin doctor /
verify / --repair to ask about the second one.

Observed with codex 0.144.6 on Linux.

Reproduction

export CODEX_HOME=$(mktemp -d)

# any marketplace works; this uses a renamed copy of the curated catalogue
git clone -q --depth 1 https://github.com/openai/plugins.git /tmp/plugins-src
cp -r /tmp/plugins-src /tmp/mkt && rm -rf /tmp/mkt/.git
python3 - <<'PY'
import json
p = "/tmp/mkt/.agents/plugins/marketplace.json"
m = json.load(open(p)); m["name"] = "repro"
m["plugins"] = [x for x in m["plugins"] if x["name"] == "superpowers"]
json.dump(m, open(p, "w"), indent=2)
PY

codex plugin marketplace add /tmp/mkt --json > /dev/null
codex plugin add superpowers@repro --json > /dev/null

codex debug prompt-input probe | grep -c 'superpowers:'      # => 14

# Remove ONLY the skills/ subtree; the version directory and config.toml survive.
find "$CODEX_HOME/plugins/cache/repro/superpowers" -maxdepth 2 -name skills -type d -exec rm -rf {} +

codex plugin list | grep superpowers
#   superpowers@repro   installed, enabled   <version>   ...
codex plugin list --json | python3 -c 'import json,sys; print([ (p["pluginId"], p["installed"], p["enabled"]) for p in json.load(sys.stdin)["installed"] ])'
#   [('superpowers@repro', True, True)]

codex debug prompt-input probe | grep -c 'superpowers:'      # => 0

Installed, enabled, and invisible — simultaneously.

The same thing happens when the whole plugins/cache/<marketplace>/<plugin>
directory is removed while config.toml keeps its [plugins."…"] block, which
is how we hit it in practice (a persistent per-repository CODEX_HOME whose
cache did not survive a container rebuild).

Impact

We provision a repository-scoped CODEX_HOME before starting codex exec. The
provisioning step used plugin list to decide whether an install was needed, so
in this state it skipped plugin add, launched the session, and the model
correctly refused the task because the mandated skills were not in its prompt.
The failure is silent from the CLI's point of view: every command exits 0 and
every status field says healthy.

It is also not self-correcting from inside a session — a codex plugin add run
by the model after the session started does not retroactively add the skills to
the already-rendered prompt.

Workaround

Do not trust plugin list for this question. Stat the payload directly, and
verify against the rendered prompt:

ls "$CODEX_HOME/plugins/cache/<marketplace>/<plugin>"/*/skills/*/SKILL.md
codex debug prompt-input probe | grep -o '<plugin>:[a-z-]*' | sort -u

codex plugin add <id> repairs both a missing and a gutted payload and is
idempotent, so it is safe to run unconditionally.

Suggested fixes (any one would be sufficient for us)

  1. Make plugin list stat the payload: report installed only when the

version directory referenced by the config entry actually exists (and,
ideally, report enabled, payload missing rather than installed, enabled).

  1. Reconcile on load: when a plugin is enabled in config.toml but its cached

payload is absent, re-materialize it from the configured marketplace instead
of rendering a session without it.

  1. Add a codex plugin doctor (or plugin list --verify) that reports

payload-vs-config divergence and can repair it, so automation has a
supported command to call.

Related but distinct: #31365 (payload present, config entry lost — the mirror
image), #21138 (payload stale because refresh compares only the version string),
#29103 (marketplace source not persisted).

View original on GitHub ↗

2 Comments

builtbyhuy · 17 days ago

Confirmed this is still reproducible on the current stable codex-cli 0.147.0 on macOS 26.5 arm64.

I reran the original scenario with isolated temporary HOME, CODEX_HOME, XDG directories, and a local shallow clone of openai/plugins; no model, API call, or personal Codex state was involved.

Observed sequence:

before payload removal:
  plugin list --json: installed=true, enabled=true, version=5.1.3
  debug prompt-input count for superpowers: 1

after moving only cache/.../5.1.3/skills out of the payload:
  plugin list --json: installed=true, enabled=true, version=5.1.3
  debug prompt-input count for superpowers: 0

So this now has cross-version evidence from 0.144.6 to 0.147.0 and reproduces outside Linux. I am inspecting the current loader/list reconciliation path for a focused regression test and minimal fix; I will link a PR here if that remains unaddressed on main.

builtbyhuy · 17 days ago

I traced the mismatch to PluginStore::is_installed: it currently answers whether an active version directory exists, while skill discovery separately requires the declared legacy skill paths to still exist. That lets list state remain installed: true after only the cached skills/ subtree is removed.

A focused fix is ready on builtbyhuy:fix/plugin-list-payload-validation at 535a454. The approach preserves the existing directory-presence meaning of is_installed for remote metadata/script attribution, adds a payload predicate only for plugin-list reconciliation, checks explicitly declared legacy skill paths, and keeps Agent Plugin conventional optional roots valid.

Regression coverage includes:

  • CLI E2E: install, remove only cached skills/, observe installed: false while enabled: true, then reinstall and confirm repair
  • legacy manifests that declare a direct skill file
  • Agent Plugin manifests with no optional skills/ directory

Observed locally: core-plugins 390/390, CLI 351/351, plus just fix for both crates and just fmt.

Per the invitation-only contribution policy, I have not opened an unsolicited PR. If this approach matches the intended fix and a maintainer would like a PR, I can submit the prepared branch.