Skip to content

Instantly share code, notes, and snippets.

@clairernovotny
Last active March 11, 2026 18:38
Show Gist options
  • Select an option

  • Save clairernovotny/89587e4932d854b10bbab913b95ecb5c to your computer and use it in GitHub Desktop.

Select an option

Save clairernovotny/89587e4932d854b10bbab913b95ecb5c to your computer and use it in GitHub Desktop.
Codex plugins and marketplaces developer notes from source analysis

Codex Plugins and Marketplaces: Developer Notes

Rescanned against openai/codex main on March 11, 2026.

Status

The plugin system is active in source, but it is still explicitly under development.

  • features.plugins exists, is stage underDevelopment, and defaults to false in features.rs.
  • codex app-server now exposes plugin/list, plugin/install, and plugin/uninstall, and the README still marks them under development in codex-rs/app-server/README.md.
  • The contract has continued to evolve after the March 6 scan: marketplace policies now exist, curated plugin state can sync from a remote ChatGPT endpoint, and uninstall is now a real API.

Mental Model

A plugin is still a local bundle of optional:

  • skills
  • MCP servers
  • app connector IDs
  • manifest-declared UI metadata

Codex still runs plugins from a local cache under $CODEX_HOME/plugins/cache/... via store.rs.

A marketplace is now more than just local discovery metadata. It is the source of:

  • plugin source locations
  • install policy
  • auth policy
  • category taxonomy
  • plugin interface data surfaced by plugin/list

Codex can also mirror an official curated marketplace from https://github.com/openai/plugins.git into $CODEX_HOME/.tmp/plugins via curated_repo.rs.

User Config Contract

Plugins remain user-config only.

  • The plugin feature gate is read only from the user config layer in manager.rs.
  • Plugin entries are also read only from the user config layer in manager.rs.
  • Project .codex/config.toml files cannot enable plugin loading or define plugin state for a checkout.

Current user config shape:

[features]
plugins = true

[plugins."my-plugin@debug"]
enabled = true

Important details:

  • The config key is still <plugin>@<marketplace>.
  • PluginConfig still only contains enabled in types.rs.
  • Plugin and marketplace names must be non-empty and limited to ASCII letters, digits, _, and - in store.rs.

Install and Cache Layout

Installed plugins still live under:

$CODEX_HOME/plugins/cache/<marketplace>/<plugin>/local

Current store behavior from store.rs:

  • install version is still hardcoded to "local"
  • reinstall overwrites the existing cache entry
  • is_installed() is now a first-class cache presence check used by plugin/list
  • uninstall() removes the cached plugin directory for that plugin key

Separately, the curated marketplace mirror lives under:

$CODEX_HOME/.tmp/plugins
$CODEX_HOME/.tmp/plugins.sha

Curated sync behavior from curated_repo.rs and manager.rs:

  • source repo is https://github.com/openai/plugins.git
  • the sync is gated by features.plugins
  • background sync can be disabled with CODEX_DISABLE_CURATED_PLUGIN_SYNC

Plugin Bundle Contract

Default layout is still recognized:

my-plugin/
  .codex-plugin/
    plugin.json
  skills/
    my-skill/
      SKILL.md
  .mcp.json
  .app.json

But the manifest is now richer and can override those default component paths.

Manifest fields

Current manifest fields from manifest.rs:

  • name
  • description
  • skills
  • mcpServers
  • apps
  • interface

Minimal manifest:

{
  "name": "my-plugin"
}

Manifest with path overrides and UI metadata:

{
  "name": "my-plugin",
  "description": "Plugin summary for instructions and listings.",
  "skills": "./plugin-skills",
  "mcpServers": "./plugin.mcp.json",
  "apps": "./plugin.apps.json",
  "interface": {
    "displayName": "My Plugin",
    "shortDescription": "Shown in listings",
    "longDescription": "Shown in richer UI details",
    "developerName": "Example, Inc.",
    "category": "Productivity",
    "capabilities": ["Interactive", "Write"],
    "websiteURL": "https://example.com/",
    "privacyPolicyURL": "https://example.com/privacy",
    "termsOfServiceURL": "https://example.com/terms",
    "defaultPrompt": "Starter prompt for trying this plugin",
    "brandColor": "#3B82F6",
    "composerIcon": "./assets/icon.png",
    "logo": "./assets/logo.png",
    "screenshots": ["./assets/shot1.png", "./assets/shot2.png"]
  }
}

Path rules from manifest.rs:

  • manifest paths must start with ./
  • manifest paths cannot be ./
  • manifest paths cannot contain ..
  • manifest paths are resolved relative to the plugin root
  • invalid override paths are ignored with warnings rather than crashing plugin load

The same rule applies to interface assets such as composerIcon, logo, and screenshots.

Skills in Plugins

Plugin skill loading is still rooted in the plugin directory, but it is now more configurable and more visible to the model.

  • If the manifest provides skills, Codex uses that path; otherwise it falls back to skills/ in manager.rs.
  • Skills are namespaced using the plugin manifest name in manager.rs and skills/loader.rs.
  • The global ## Plugins instruction block is still injected into user instructions via render.rs and project_doc.rs.

Explicit plugin mentions now inject targeted developer guidance for that plugin via plugins/injection.rs and render.rs.

Tested behavior in codex-rs/core/tests/suite/plugins.rs:

  • explicit plugin mention adds guidance about skill prefixes
  • explicit plugin mention adds guidance about visible MCP servers
  • explicit plugin mention adds guidance about visible apps
  • plugin-associated tool descriptions include plugin provenance

MCP Servers in Plugins

Plugin MCP loading still mirrors normal mcpServers config, but manifest path overrides now apply here too.

  • If the manifest provides mcpServers, Codex loads that file; otherwise it falls back to .mcp.json in manager.rs.
  • Relative cwd values inside plugin MCP configs are still rebased to the plugin root in manager.rs.
  • oauth.callbackPort is still ignored for plugin MCP configs in manager.rs.
  • Duplicate MCP server names across plugins are still skipped in manager.rs.
  • User-configured MCP servers still win over plugin MCP servers in mcp/mod.rs.

Apps in Plugins

Plugin app loading also supports manifest path overrides now.

  • If the manifest provides apps, Codex loads that file; otherwise it falls back to .app.json in manager.rs.
  • The file contents are still a mapping of arbitrary entry names to connector IDs.
  • Connectors now carry plugin_display_names provenance in connectors.rs.
  • Accessible app connectors inherit plugin provenance from MCP tools in connectors.rs.

Install-time behavior from codex_message_processor.rs:

  • plugin/install returns appsNeedingAuth for plugin apps that exist in the app directory but are not yet accessible
  • plugin/install also returns the marketplace-declared plugin auth policy

Marketplace Contract

Marketplace files must satisfy the contract in marketplace.rs:

  • the file must live at <root>/.agents/plugins/marketplace.json
  • source paths are local only
  • source paths must start with ./
  • source paths are resolved relative to <root>, not relative to .agents/plugins/

Current marketplace format:

{
  "name": "debug",
  "plugins": [
    {
      "name": "my-plugin",
      "source": {
        "source": "local",
        "path": "./plugins/my-plugin"
      },
      "installPolicy": "AVAILABLE",
      "authPolicy": "ON_USE",
      "category": "Productivity"
    }
  ]
}

Marketplace-driven policy and taxonomy fields from marketplace.rs:

  • installPolicy
  • authPolicy
  • category

Current install policy enum values:

  • NOT_AVAILABLE
  • AVAILABLE
  • INSTALLED_BY_DEFAULT

Current auth policy enum values:

  • ON_INSTALL
  • ON_USE

Important marketplace behavior:

  • plugin/install rejects plugins marked NOT_AVAILABLE
  • marketplace category overrides manifest interface category when both are present
  • authPolicy is carried through install and returned to the client

Marketplace Discovery and Deduplication

plugin/list is now the authoritative discovery surface.

Discovery rules come from marketplace.rs and manager.rs:

  • home marketplace discovery is always considered first
  • additional repo roots come from plugin/list.cwds
  • the curated repo root is appended automatically when the synced mirror exists
  • marketplace files with the same name can coexist as separate entries if they come from different paths
  • duplicate plugin keys are deduped in plugin/list, and the first discovered source wins

In practice:

  • home-scoped duplicates beat repo-scoped duplicates
  • later marketplaces may still appear in the response, but duplicate plugin entries are removed once an earlier source already claimed that plugin key

App-Server API

plugin/list

Current protocol shape from v2.rs:

{
  "method": "plugin/list",
  "id": 1,
  "params": {
    "cwds": ["/absolute/workspace/path"],
    "forceRemoteSync": false
  }
}

cwds is optional. If omitted, only home-scoped marketplaces and the curated marketplace are considered.

forceRemoteSync is new:

  • when true, app-server attempts to reconcile the curated marketplace against remote ChatGPT plugin state before returning results
  • if that sync fails, plugin/list still returns local marketplace data and sets remoteSyncError

Current response additions from v2.rs:

  • remoteSyncError
  • installPolicy on each plugin
  • authPolicy on each plugin

Trimmed response shape:

{
  "marketplaces": [
    {
      "name": "debug",
      "path": "/abs/path/to/.agents/plugins/marketplace.json",
      "plugins": [
        {
          "id": "my-plugin@debug",
          "name": "my-plugin",
          "source": {
            "type": "local",
            "path": "/abs/path/to/plugin/root"
          },
          "installed": false,
          "enabled": false,
          "installPolicy": "AVAILABLE",
          "authPolicy": "ON_USE",
          "interface": {
            "displayName": "My Plugin"
          }
        }
      ]
    }
  ],
  "remoteSyncError": null
}

This is implemented in codex_message_processor.rs and tested in plugin_list.rs.

plugin/install

Current protocol shape from v2.rs:

{
  "method": "plugin/install",
  "id": 2,
  "params": {
    "marketplacePath": "/abs/path/to/.agents/plugins/marketplace.json",
    "pluginName": "my-plugin"
  }
}

Current response shape:

{
  "authPolicy": "ON_USE",
  "appsNeedingAuth": [
    {
      "id": "calendar",
      "name": "Google Calendar",
      "description": "Plan events",
      "installUrl": "https://chatgpt.com/apps/google-calendar/calendar"
    }
  ]
}

Important behavior from codex_message_processor.rs and plugin_install.rs:

  • install still copies into the local cache and writes enabled = true to user config
  • install now returns marketplace authPolicy
  • install rejects marketplace entries marked NOT_AVAILABLE
  • install still computes appsNeedingAuth for plugin apps that are present but not accessible

plugin/uninstall

Uninstall is now a real API.

Current protocol shape from v2.rs:

{
  "method": "plugin/uninstall",
  "id": 3,
  "params": {
    "pluginId": "my-plugin@debug"
  }
}

Current response:

{}

Behavior from manager.rs, store.rs, and plugin_uninstall.rs:

  • removes the plugin cache directory
  • clears the plugin’s user-level config entry
  • is idempotent enough to allow repeated uninstall calls

Curated Remote Sync

This is the biggest new capability since the March 6 scan.

plugin/list(forceRemoteSync: true) can reconcile the local openai-curated marketplace against remote ChatGPT plugin status via manager.rs.

High-level behavior:

  • only the curated marketplace participates
  • ChatGPT auth is required
  • API key auth is explicitly unsupported
  • remote state can cause installs, enables, disables, and uninstalls in the local curated cache and config
  • sync is fail-open for plugin/list: on sync failure, app-server still returns local data and fills remoteSyncError

Remote sync result categories in manager.rs:

  • installed_plugin_ids
  • enabled_plugin_ids
  • disabled_plugin_ids
  • uninstalled_plugin_ids

Remote sync is exercised by tests in plugin_list.rs and manager.rs.

Mention / Invocation Contract

The app-server README still documents plugin mentions directly.

  • UI text can include a plugin mention token like @sample
  • the structured mention path should be plugin://<plugin-name>@<marketplace-name>
  • explicit plugin mention remains the mechanism for injecting plugin-scoped guidance into a turn

Relevant docs and tests:

Example structured input:

{
  "type": "mention",
  "name": "sample",
  "path": "plugin://sample@test"
}

Install, Update, Disable, Remove

Current install flow:

  1. Call plugin/list.
  2. Pick a marketplace entry and plugin entry.
  3. Call plugin/install with the returned marketplacePath and plugin name.
  4. Codex copies the plugin into the cache.
  5. Codex writes enabled = true under plugins."<plugin>@<marketplace>".
  6. Codex clears plugin and skill caches.
  7. Codex returns authPolicy and, if applicable, appsNeedingAuth.

Current update story:

  1. Change the local source plugin directory.
  2. Call plugin/install again.
  3. Codex overwrites the cached local version.

Disable is still config-driven:

[plugins."my-plugin@debug"]
enabled = false

Remove is now API-backed:

  1. Call plugin/uninstall.
  2. Codex deletes the cached plugin directory.
  3. Codex removes the plugin entry from user config.

Current Bottom Line

What Codex has as of March 11, 2026:

  • a user-config-only plugin feature gate
  • cache-backed installed plugins
  • manifest-declared metadata and component path overrides
  • marketplace-declared install and auth policies
  • a first-class plugin/list API
  • an explicit-path plugin/install API
  • a real plugin/uninstall API
  • install-time app auth hints and auth policy return values
  • explicit plugin mention guidance injection
  • a synced curated catalog from openai/plugins
  • optional remote reconciliation of curated plugin state via plugin/list(forceRemoteSync: true)

What it still does not have:

  • remote plugin package downloads beyond marketplace-declared local source paths
  • semver or multi-version installs
  • a fully documented stable production contract

Source Files Worth Reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment