Deleting a workspace plugin reserves its name for a period, blocking re-upload, with no message saying so

Open 💬 1 comment Opened Aug 19, 2026 by kerrhome

What happened?

Deleting a private workspace plugin from the admin plugin catalog appears to leave its name permanently reserved. A subsequent upload of a new version of the same plugin is refused with:

A plugin named <plugin-name> already exists.

The plugin is gone from the catalog, but the name cannot be reused. Waiting several hours did not clear it.

This matters because it removes the last working route for shipping a plugin update. The supported in-place update path is broken by #36929 (/public/plugins/workspace/upload-url returns 404), and the admin-catalog workaround for that is delete-then-upload, since the admin UI offers upload, delete and disable but no update action. With the name retained after deletion, both routes are now dead ends, and the old version has already been deleted by the time you find out.

Steps to reproduce

  1. As a workspace owner/admin, have a private workspace plugin published in the admin plugin catalog at version X.
  2. Delete it from the admin plugin catalog.
  3. Confirm it is gone from the catalog listing.
  4. Upload a package of the same plugin at version Y, identical in name, differing only in version.
  5. The upload is refused with A plugin named <plugin-name> already exists.

Evidence that the deletion completed

The client's own plugin catalog cache under ~/.codex/cache/remote_plugin_catalog/ was refetched after the deletion. Across all three cached scopes, the name is absent:

| Scope | Entries | Contains the deleted name |
| --- | --- | --- |
| WORKSPACE | 1 | no |
| GLOBAL | 2929 | no |
| third scope | 0 | no |

There is also no local install holding the name; ~/.codex/plugins/ contained only staging and cache directories.

Evidence the package is not at fault

Diffing the archive that uploaded successfully the previous day against the one now refused:

  • .codex-plugin/plugin.json differs in exactly one line, the version string. The name field is byte-identical, as are description and the interface display fields.
  • The two archives contain an identical set of file paths. No files added or removed.
  • The internal top-level directory is identical in both.
  • The layout matches https://developers.openai.com/plugins/build/plugins : only plugin.json inside .codex-plugin/, with skills/, assets/ and .mcp.json at the plugin root.
  • Gzipped tar, 55 KB. The version that uploaded fine was 3.8 MB, so the refused one is smaller.

The upload dialog accepts the file and reports the name collision, so this is not a package validation failure.

No API route to inspect or release the name

A workspace admin key authenticates correctly against GET https://api.chatgpt.com/v1/analytics/codex/workspaces/{workspace_id}/usage (HTTP 200), but every plugin-management path returns 401, so there is no programmatic way to see or clear the retained record.

Expected behavior

Either:

  • deleting a workspace plugin should release its name for re-upload; or
  • if the name is intentionally retained, the delete action should say so before deleting, since deletion is currently irreversible and destroys the working version; and
  • the documentation should state the retention, and the supported way to publish a new version of an existing workspace plugin.

Questions

  1. Is name retention after deletion intended, and if so for what period?
  2. Is uploading a new version alongside the existing one supported, so the old entry is removed only after the new upload succeeds? If so that is the procedure to document, and it avoids this entirely.
  3. Is the uniqueness constraint scoped to the workspace, the creating account, or global? The name does not appear in any catalog scope visible to the client.

Renaming the plugin is not a viable workaround: the name is how existing installs resolve, so renaming orphans every current user.

Related

  • #36929 — the in-place update path returns 404 for a shared Business workspace plugin. Same underlying goal, different route. That issue's workaround is what leads here.
  • #22885 — the same backend plugin route returning 404 through another path.

No tokens, workspace identifiers, plugin contents, hostnames, or customer data are included.

View original on GitHub ↗

1 Comment

kerrhome · 8 days ago

Update: this resolved on its own, so the reservation is temporary rather than permanent. Correcting the report.

The same package uploaded successfully with no change to it and no action taken on the name. It is now published and enabled at the new version, under a new plugin id, so the server created a fresh record rather than reviving the deleted one.

Timing, with a defensible lower bound rather than a guess. A catalog fetch at 15:12:10Z already showed the plugin absent, so the deletion had completed by then. Repeated upload attempts were refused after that point. The successful upload was recorded at 16:09:36Z. So the name stayed reserved for at least 57 minutes after deletion was observably complete. I cannot pin the exact deletion time, so the true window is longer than that, not shorter.

What that changes about the report:

  • The title's "permanently" is wrong, and I have corrected it.
  • The bug is still real, and in some ways the temporary version is worse than a permanent one, because the failure mode is indistinguishable from a permanent block while you are in it. There is no message saying the name is held temporarily, no indication of how long, and no way to query it. The reasonable reading of "A plugin named X already exists" while looking at a catalog that does not contain X is that something is broken and needs support, not that waiting an hour fixes it.
  • The destructive ordering is unchanged and is the part worth fixing. The delete removes the working version first, and then there is a window of unknown length in which the replacement cannot be published. During that window a workspace has no published plugin.

So the useful asks are narrower than my original three:

  1. Document that the name is held after deletion, and for how long.
  2. Say so in the delete confirmation, before the delete, since that is when the information matters.
  3. Better, make the error message distinguish "this name is temporarily reserved from a recent deletion, try again later" from "this name is in use", because those need opposite responses from the operator.

None of that is urgent for us any more. Leaving it open because the ordering trap and the undiagnosable error message are both still present for the next person, and #36929, which is what pushes people down this path, is still open.