Local Plugin UI Containment Bug in Codex App

Open 💬 4 comments Opened Apr 3, 2026 by jkaunert
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

Version 26.325.31654 (1272)

What subscription do you have?

Pro

What platform is your computer?

Darwin 24.6.0 arm64 arm

What issue are you seeing?

A local skill-bearing plugin installs cleanly and the CLI/runtime surface is
namespaced correctly, but the Codex Electron app still exposes plugin-owned
skills as directly invokable items in the picker and Skills UI. The same app
also renders local marketplace metadata differently from the curated
Build iOS Apps plugin.

The core issue is that local plugin-owned skills are not visually contained
behind the plugin entrypoint in Electron, even though the runtime itself is
cleanly namespaced.

What is already proven clean:

  1. plugin-managed runtime install is proven through installed cache, enabled

config state, and fresh-session visibility

  1. local plugin source is a materialized directory at

~/plugins/apple-appdev-workflow

  1. plugin payload no longer ships node_modules/
  2. CLI skill listing is namespaced and clean, for example:
  • apple-appdev-workflow:apple-product-surface-orchestrator
  • apple-appdev-workflow:apple-persistence-orchestrator
  • apple-appdev-workflow:apple-swiftdata-review
  1. explicit plugin-entry controls are green for both product-surface and

persistence brigades

Remaining Electron-only defects:

  1. plugin-owned skills still surface outside the plugin entrypoint in

picker/Skills UI

  1. plugin card metadata uses local marketplace name in the Category row

instead of marketplace interface.displayName

  1. local plugin presentation differs from curated plugin presentation even when

plugin-level and skill-level metadata are aligned closely with
Build iOS Apps

Exact text error encountered during local marketplace metadata testing:

Failed to load plugin
invalid marketplace name: only ASCII letters, digits, '_', and '-' are allowed

Exact prompt examples used during validation:

Use [@apple-appdev-workflow](plugin://apple-appdev-workflow@LocalAppleWorkflow) to review the SwiftData model and migration approach for saved reference captures, and recommend what should change first to reduce rollout and data-loss risk.
Review the SwiftData model and migration approach for saved reference captures, and recommend what should change first to reduce rollout and data-loss risk.
Use apple-product-surface-orchestrator to review this SwiftUI app's main product surface and recommend what should change first so the navigation, empty states, and on-screen copy feel more coherent and production-ready.

Representative CLI validation commands:

python3 ./scripts/probe_plugin_managed_runtime.py --repo-root .
codex exec -C /tmp --skip-git-repo-check "List the available plugins in this session by name only."
codex exec -C /tmp --skip-git-repo-check "List the available skills in this session by name only."

Representative clean runtime conclusion:

plugin-managed runtime is proven for the target plugin through installed cache, enabled config state, and fresh-session visibility

Electron-specific text clues observed in app-side logging / Sentry scope:

Skills/list missing short_description ...
method=plugin/read

Why this appears platform-side rather than bundle-side:

  1. first-party Build iOS Apps is also a real skills plugin
  2. its installed cache includes skills/ and skill-level agents/openai.yaml
  3. CLI exposes those skills as namespaced runtime skills
  4. the local plugin runtime is also namespaced cleanly in CLI
  5. the exposed/duplicate behavior persists primarily in Electron UI surfaces

rather than in the runtime listing itself

What steps can reproduce the bug?

  1. Create or use a local plugin marketplace with a local skill-bearing plugin.

In my case:

  • marketplace name: LocalAppleWorkflow
  • marketplace display name: Local Apple Workflow
  • plugin id: apple-appdev-workflow
  1. Install the plugin from the local marketplace in the Codex Electron app.
  2. Confirm the runtime itself is healthy:
  • run python3 ./scripts/probe_plugin_managed_runtime.py --repo-root .
  • confirm the probe concludes:

plugin-managed runtime is proven for the target plugin through installed cache, enabled config state, and fresh-session visibility

  1. Confirm CLI runtime namespacing is clean:
  • run codex exec -C /tmp --skip-git-repo-check "List the available plugins in this session by name only."
  • run codex exec -C /tmp --skip-git-repo-check "List the available skills in this session by name only."
  • confirm only namespaced skill entries appear for the local plugin
  1. Open the plugin card, picker, and Skills tab in the Electron app.
  2. Observe that plugin-owned skills are exposed as directly invokable UI items

outside the plugin entrypoint, and that the plugin card Category row uses the
marketplace slug-style name rather than the human-facing marketplace
interface.displayName.

What is the expected behavior?

For local skill-bearing plugins, Electron should either:

  1. preserve plugin containment rules consistently with curated plugins
  2. or document that local skill-bearing plugins are intentionally exposed as

standalone picker/Skills items

Expected metadata behavior:

  1. human-facing marketplace labels should use marketplace

interface.displayName

  1. slug-style marketplace name should remain an internal identifier and not

leak into the plugin card Category row

Instead, I see:

  1. The local plugin runtime is clean in CLI, but Electron still exposes

plugin-owned skills as standalone invokable items in picker/Skills UI.

  1. The plugin card Category row uses the marketplace slug-style identifier

instead of the human-facing display name.

  1. Curated Build iOS Apps is presented differently in Electron despite both

plugins being real skill-bearing plugins with runtime-visible skills/.

Representative local plugin runtime entries:

apple-appdev-workflow:apple-product-surface-orchestrator
apple-appdev-workflow:apple-persistence-orchestrator
apple-appdev-workflow:apple-swiftdata-review

Representative curated plugin cache root:

~/.codex/plugins/cache/openai-curated/build-ios-apps/...

Additional information

PII-redacted path examples:

/Users/<redacted>/.codex/plugins/cache/LocalAppleWorkflow/apple-appdev-workflow/local/skills/apple-product-surface-orchestrator/SKILL.md
/Users/<redacted>/.codex/plugins/cache/openai-curated/build-ios-apps/...

The local plugin and curated plugin both have:

  1. skills/
  2. plugin-level agents/openai.yaml
  3. skill-level agents/openai.yaml

I did not find an explicit allow_implicit_invocation: false in the curated
plugin files I inspected, so the different Electron presentation does not look
like a simple manifest-field difference.

View original on GitHub ↗

4 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #16298
  • #16006
  • #16133

Powered by Codex Action

jkaunert · 4 months ago

I have a concrete runtime-level follow-up for this issue.

During local-plugin validation, I found that the containment problem is not only UI/state. There is also a runtime aliasing seam that makes plugin-owned skills usable through bare skill names even after they were loaded with a canonical plugin namespace.

What I found:

  • Plugin skills are loaded with a canonical namespaced id like plugin_id:skill_id.
  • Later, plain-text skill matching deliberately accepts the plugin skill's bare base name too.
  • In practice, that means a local plugin skill can still be selected from text like apple-release-orchestrator instead of only apple-appdev-workflow:apple-release-orchestrator or a structured chip/skill input.

Why this matters for #16663:

  • It weakens plugin containment.
  • It makes local plugin skills look directly invocable from the generic skills surface even when the plugin-qualified identity is the real canonical one.
  • It creates exactly the kind of ambiguous "plugin-managed vs free-floating skill" behavior we have been seeing in the app.

The relevant runtime shape is:

  • canonical namespacing at load time
  • later plain-text alias matching that re-adds the plugin base name path

That seems like the wrong default for plugin-contributed skills.

The narrow fix shape that worked well locally was:

  1. keep structured skill inputs / chips working
  2. keep fully qualified $plugin:skill text mentions working
  3. stop resolving bare plugin skill names from plain text by default, or gate that behavior behind a config/feature flag

Important nuance: this is separate from allow_implicit_invocation. We validated that the policy flag is not the right containment lever here. The real seam is the later plain-text alias matching.

I think this issue is the right home for that observation because it explains why local plugin skills can escape strict plugin-managed routing even when the UI/plugin surface itself is the thing under investigation.

AdiHuang · 3 months ago

I can reproduce a closely related UI/source attribution issue on macOS.

In my Codex Desktop Skills UI, skills that are clearly provided by plugins are displayed under the Chinese label 个人 / Personal. That label reads as if the skill is user-created or locally installed, but the on-disk provenance shows otherwise.

Environment observed:

  • Codex CLI: codex-cli 0.128.0
  • Platform: Darwin 25.4.0 arm64 / macOS 26.4.1
  • GitHub user session: regular signed-in browser session

On-disk skill provenance:

  • User/local skills: ~/.codex/skills/*/SKILL.md — 24 observed
  • System skills: ~/.codex/skills/.system/*/SKILL.md — 5 observed
  • Plugin skills: ~/.codex/plugins/cache/.../skills/*/SKILL.md — 58 observed

Examples of plugin-owned skills that are nevertheless shown as Personal in the UI:

  • ~/.codex/plugins/cache/openai-curated/build-web-apps/.../skills/frontend-app-builder/SKILL.md
  • ~/.codex/plugins/cache/openai-curated/github/.../skills/github/SKILL.md
  • ~/.codex/plugins/cache/openai-curated/superpowers/.../skills/using-superpowers/SKILL.md
  • ~/.codex/plugins/cache/openai-bundled/browser-use/.../skills/browser/SKILL.md

The plugin-level metadata exists in .codex-plugin/plugin.json and includes fields such as name, author, interface.displayName, and interface.developerName. However, individual SKILL.md frontmatter only has name and description, so the app appears to flatten all available skills into the user's personal skill bucket.

Expected behavior:

  • Skills UI should distinguish at least Personal / local, System, and Plugin: <plugin display name>.
  • Plugin-provided skills should not appear as if the user personally created them.

Observed behavior:

  • The UI label 个人 / Personal is applied to plugin-provided skills, which makes source auditing confusing when dozens of skills are installed.

This looks like the same broader containment/provenance problem described here, but specifically for the skill source label shown in Codex Desktop.

jkaunert · 3 months ago

Current structured-provenance follow-up for this issue:

I tested a local app-server/runtime experiment that goes beyond the older flat pluginId approach. The experiment adds structured skill provenance plus compact plugin metadata to the skills/list payload.

What the app-server returned in direct and packaged probes:

  • 48 total skills
  • 41 apple-appdev-workflow skills
  • every Apple workflow skill had provenance = custom_marketplace
  • every Apple workflow skill had plugin.id = apple-appdev-workflow@LocalAppleWorkflow
  • zero Apple workflow skills were missing plugin metadata

I then tested that patched app-server inside disposable Electron app bundles copied from newer hosts:

  • host 26.513.31313 / Electron 42.0.1
  • host 26.519.31651 / Electron 42.1.0

For both disposable candidates:

  • the app bundle was re-signed locally
  • packaged app-server schema preflight passed
  • direct packaged skills/list provenance probe passed
  • GUI smoke passed after the required macOS authorization prompts

Result: the Electron Skills UI still grouped those plugin-owned Apple workflow skills under Personal on the latest tested host, even though the app-server wire payload contained enough plugin provenance to distinguish them from ordinary user skills.

This narrows the remaining issue from “the app-server lacks plugin attribution” to “the Electron Skills page does not consume plugin provenance/plugin metadata for grouping or filtering.”

A source-level fix shape that would address the observed behavior:

  1. Preserve plugin ownership/provenance on skills/list entries.
  2. In the Electron Skills page, group or filter installed skills using skill.plugin / skill.provenance or equivalent plugin-owned metadata before falling back to scope = user.
  3. Do not render plugin-owned skills under Personal; either keep them behind the plugin entrypoint or show them under a plugin-owned section labeled with the plugin display name.
  4. Use the filtered/grouped list for empty-state and installed-count decisions so plugin-owned skills do not inflate the Personal section.

This also matches the separate community repro above where plugin cache paths clearly show plugin-owned skills but the desktop UI labels them as Personal.