Codex plugin add misses the root plugin.json the list already found

John Burns

The marketplace entry said 4.5.6. The root plugin.json said 1.2.3. The file under .codex-plugin/ said 9.9.9. codex plugin add reported 9.9.9. It would not have reported any of those strings if the third file had been absent. On that package the same command exited 1, printed missing plugin.json, and did not name the path it had failed to open.

codex plugin list had already printed the source directory. The only manifest in that directory was plugin.json at the plugin root. The file add required was .codex-plugin/plugin.json. Those are not two prints of one file. List can resolve a portable package, and add can still refuse it, before any version comparison runs.

What the binary was, and what it was not asked to do

The binary was Codex CLI 0.163.0 on Linux x86_64. codex --version printed codex-cli 0.163.0. The GitHub release rust-v0.163.0 is dated 15 October 2026. This run was 5 November 2026. The binary was not replaced with 0.166.0 or 0.167.0, which other release notes already describe. Nothing below is a claim about those later builds.

No codex login was run. No prompt was sent. No interactive session was started, so this note does not say a skill loaded, a hook ran, or a model saw the package. Each case used a new CODEX_HOME. The normal Codex home was not the directory under test. The isolated home sat under a path this binary treats as temporary, so every command also printed a warning that it would not create PATH-alias helper binaries, then continued. A home that is not under that kind of path may omit the warning. The exit codes below are the plugin commands, not that warning.

The packaging page, read the same day, says a portable Agent Plugins package puts plugin.json at the plugin root and declares the Agent Plugins schema. It says an existing .codex-plugin/plugin.json remains a compatibility fallback, and that the plugin-creator scaffold still writes that fallback rather than the portable layout. It says codex plugin marketplace add tracks a marketplace source, and it tells you to use the ChatGPT desktop app to install and test a local plugin. This run did not open that app. It used codex plugin add, which this binary’s own help describes as installing a plugin from a configured marketplace. The page does not say that CLI add ignores a root plugin.json. It does not say the missing-file error omits the path. It does not say the version in the add result is the overlay version when the root file and the marketplace entry disagree.

List resolves the directory add will not read

Each marketplace was a directory. The catalog was .agents/plugins/marketplace.json at that directory. One entry named the plugin and set source to a local object whose path was ./plugins/<plugin>, relative to the marketplace root, not relative to the catalog file. The plugin directory held a regular file, plugin.json, and in one case a skill at skills/probe-skill/SKILL.md. The root manifest was not a symlink. Its sha256 began ec5cc3b8.

codex plugin marketplace add <market> --json on that directory exited 0. The JSON set marketplaceName from the catalog name, installedRoot to the directory that was passed, and alreadyAdded to false. The config file gained a [marketplaces.<name>] table with source_type = "local" and source set to that same directory. Passing the catalog file itself, instead of the directory, exited 1:

Error: local marketplace source must be a directory, not a file

That refusal matches the help examples, which take a marketplace root. It is a different failure from missing plugin.json. A job that adds the JSON path stops before list ever prints a plugin. A job that adds the directory gets a catalog, and the next failure is inside the plugin.

codex plugin list then printed the marketplace name, the catalog path, and one row. For the root-only package the status was not installed, the version cell was empty, and the source cell was the plugin directory that contained the root plugin.json. The command exited 0. The empty version cell is not the install error. The install error comes from the next command.

codex plugin add probe-plugin@probe-market --json exited 1. Stdout had no JSON object. The error was exactly:

Error: missing plugin.json

Caused by:
    missing plugin.json

The root file was still in the source directory list had printed. No cache directory was created for that plugin. A second list printed the same not-installed row and the same empty version cell. Marketplace add had succeeded. The package was visible. Add had not installed it, and the sentence it printed did not say .codex-plugin/plugin.json.

The overlay is the manifest add reads

The same layout with one extra file changed the exit code. Beside the root plugin.json, the plugin had .codex-plugin/plugin.json. Both files said version 0.0.1 and the same plugin name as the marketplace entry. List, before add, still showed not installed and a blank version. The overlay’s presence did not fill that cell.

codex plugin add probe-plugin@probe-market-overlay --json exited 0. The JSON named the plugin id, the marketplace, version 0.0.1, an installedPath under <codex-home>/plugins/cache/<market>/<plugin>/0.0.1, and authPolicy ON_INSTALL. The marketplace entry in that case had no policy block. The field still appeared. The command did not prompt. A later list row said installed, enabled, version 0.0.1, and the source cell was still the marketplace plugin directory, not the cache path.

An overlay-only package, with no root plugin.json and with version 9.9.9 only in .codex-plugin/plugin.json, listed the same way before install: not installed, blank version, source directory present. Add exited 0 and reported 9.9.9. The cache for that package contained the overlay file and did not contain a root plugin.json. So a root manifest is not required for this CLI add, and it is not sufficient. The compatibility file is the one this binary opens.

The split package is the one that shows which version wins. The marketplace entry carried "version": "4.5.6". The root file carried "version": "1.2.3". The overlay carried "version": "9.9.9". List before add still left the version cell blank, so the cell is not reading the marketplace entry either. Add exited 0 and reported 9.9.9. The cache directory was .../split-version/9.9.9. The copied root file in that directory still said 1.2.3. The copied overlay still said 9.9.9. Add stored both texts and selected the overlay’s version for the result and for the cache path. A job that prints the root file after a successful add, and treats that version as the installed version, is reading the file add did not use.

A name mismatch is a different exit 1, and it is specific. The marketplace entry name was entry-name. The overlay name was other-name. There was no root plugin.json. Add exited 1 with:

Error: plugin.json name `other-name` does not match marketplace plugin name `entry-name`

Caused by:
    plugin.json name `other-name` does not match marketplace plugin name `entry-name`

That sentence names both strings. The missing-file sentence names neither path. If add says the names disagree, the overlay was found. If add says only missing plugin.json, this binary did not find .codex-plugin/plugin.json, whether or not a root file is sitting in the directory list already printed.

A blank version is the row, not the file

Before install, every package in this run had a blank version cell. That included the overlay-only package whose only manifest said 9.9.9, and the split package whose marketplace entry said 4.5.6. After a successful add, the cell showed the overlay version, and the status became installed, enabled. The source cell did not move. It stayed on the marketplace tree.

Do not use the blank cell as proof the overlay is missing. Do not use a filled cell as proof the root file was selected. Read the overlay, or read the version in the add JSON and the last path segment of installedPath. Those two agreed with each other in every successful case here, and both agreed with the overlay, not with the root file and not with the marketplace entry.

The config write is a third place, and it does not store the version. After a successful add the file contained:

[plugins."split-version@probe-market-split"]
enabled = true

The failed root-only add did not write a plugins table for that id. Enabled in this file means the CLI add completed. It does not mean a session loaded the skill. This run never started one. The packaging page says installing or enabling a plugin does not automatically trust bundled hooks, and that Codex skips those hooks until the current definition is reviewed. Nothing here contradicts that. Nothing here tests it.

authPolicy in the success JSON was ON_INSTALL even when the catalog entry omitted policy. That string was not a prompt. The non-interactive command exited 0 without asking. Do not read the field as evidence that a later interactive install will also skip a question, and do not read it as evidence that a skill ran.

What the cache keeps

The cache is a copy taken at add time. List’s source column remains the live marketplace path. The add JSON’s installedPath is the copy. For the package that had a root manifest, an overlay, and a skill, the copy contained all three: the root plugin.json with its unused version, the overlay, and skills/probe-skill/SKILL.md. For the overlay-only package, the copy contained the overlay and nothing else, because nothing else was in the source.

This run did not edit the marketplace tree after add and then start a session, so it does not say whether a later session reads the cache or the source path. The useful check at add time is the copy’s directory name and the overlay inside it. If the directory ends in the root file’s version, this note does not describe that binary. On 0.163.0 the directory ended in the overlay version, including when that number appeared in neither the root file nor the marketplace entry.

Check both paths before add

Use a disposable CODEX_HOME if the config write must not land in the operator home. Confirm codex --version before comparing a result with this note. Then add the marketplace directory, not the catalog file:

codex plugin marketplace add <market> --json
codex plugin list

Require exit 0 from both. The list source cell should be the plugin directory you meant. If add of the catalog file exits 1 with local marketplace source must be a directory, not a file, you passed the JSON path. That is not the missing-manifest failure.

Look at the plugin directory before add:

test -f <plugin>/plugin.json && echo root
test -f <plugin>/.codex-plugin/plugin.json && echo overlay

If only the root file exists, expect codex plugin add <plugin>@<market> --json to exit 1 with missing plugin.json and no path. Do not treat a readable root plugin.json as the file that command opens. Copy or write .codex-plugin/plugin.json if this CLI is the installer you are using. Set its name to the marketplace entry name. Set its version to the version you want in the add JSON and in the cache directory. A different version in the root file, or on the marketplace entry, did not win on this binary.

After a successful add, require the JSON version and the last segment of installedPath to match the overlay. Require the next list row to say installed, enabled with that version, and require the source cell to remain the marketplace path. Require [plugins."<plugin>@<market>"] with enabled = true in the isolated config. Then stop. A session, a hook trust decision, and a desktop install are separate checks. This binary’s packaging page still points local plugin testing at the desktop app. The CLI result is enough to fail a job that only checks the root manifest.

A later Codex build may read the portable file. Pin 0.163.0, or re-run the root-only add and keep the error text, before treating a green marketplace add as an install. The catalog can be valid, the source path can be the directory you wrote, and add can still be looking one directory down, in a file the error does not name.

Sources

  • Codex CLI 0.163.0 (codex --version printed codex-cli 0.163.0) on Linux x86_64, 5 November 2026. Isolated CODEX_HOME. No login, no prompt, and no session. Commands were codex plugin marketplace add, codex plugin list, and codex plugin add --json, followed by the isolated config.toml and the plugin cache tree.
  • OpenAI Codex release rust-v0.163.0, published 15 October 2026: Release 0.163.0. Later 0.166 and 0.167 notes were not installed here.
  • OpenAI plugin packaging page, read 5 November 2026: Package your plugin. It describes the portable root plugin.json, the .codex-plugin/plugin.json compatibility fallback, codex plugin marketplace add, and the desktop app as the local install-and-test path.