Plugins have no supported runtime dependency installation contract

Open 💬 0 comments Opened Aug 27, 2026 by DuaneNielsen

What feature or behavior is missing?

Codex plugins have no supported way to declare, install locally, and preserve runtime dependencies for plugin-bundled scripts and hooks.

The official packaging documentation shows a Python lifecycle hook invoked as:

python3 ${PLUGIN_ROOT}/hooks/session_start.py

That example necessarily relies on the host system Python. It works only when the script uses the standard library or when every required package happens to be installed globally.

The documentation defines no plugin contract for:

  • Python requirements or lockfiles;
  • creation of a plugin-local virtual environment;
  • npm, Bun, or another package-manager production install;
  • an install/build lifecycle step;
  • interpreter version constraints;
  • an immutable built artifact created during installation.

Documentation: https://developers.openai.com/plugins/build/plugins

Why this matters

Using system Python is not dependency management. It makes plugin behavior depend on unrelated host state and risks package/version conflicts.

Using a virtual environment merely moves the problem outside Codex:

  • somebody must create and update it;
  • its interpreter and entry points commonly use symlinks;
  • local plugin cache copying currently drops symlinks (#18863);
  • the environment can retain absolute paths to the source checkout;
  • it is not recreated as part of the plugin installation transaction.

The same problem applies to JavaScript and TypeScript plugins. A package.json can declare runtime dependencies, but Codex does not install them when it materializes a local marketplace plugin.

Reproduction

Environment:

  • codex-cli 0.150.0
  • Linux 6.14 x86_64
  • Bun 1.3.10
  • Python 3.12.3

We maintain a TypeScript/Bun plugin with PreToolUse hooks. Its package.json declares production dependencies including marked.

  1. Install the plugin from a local marketplace.
  2. Codex copies it to plugins/cache/<marketplace>/<plugin>/<version>.
  3. Invoke the hook.
  4. Bun exits 1 because marked is absent.
  5. Run bun install --production manually inside the cached plugin.
  6. The same hook exits 0.
  7. Allow a later Codex plugin refresh to occur.
  8. Codex replaces the cache from source, removes the installed dependencies, and the hook returns to exit 1.

A numeric version bump changes the selected cache directory but does not materialize dependencies.

This is not only the symlink omission in #18863. Even a fully real, non-symlinked node_modules installed directly in the cache is later removed by refresh. Related lifecycle reports include #21138, #23902, and #31383.

Native compilation is not a general workaround

Compiling each script to a native executable would require OS/architecture-qualified artifacts and compatibility gating, which the plugin contract also lacks. See #40461, #38574, and #33391.

Bundling dependencies into one portable JavaScript file may work for pure-JavaScript dependency graphs, but it is not a general dependency model and fails for packages with native components.

Expected behavior

Codex should define and implement at least one durable local dependency contract for installed plugins. Reasonable designs include:

  1. Install declared production dependencies into a staged plugin root, then atomically activate that immutable dependency-complete root.
  2. Support language-specific locked environments, such as Python lockfiles and Node/Bun package lockfiles, with an explicit interpreter/runtime contract.
  3. Support a plugin-provided build/install step whose output becomes the immutable installed artifact.
  4. Permit a manifest to reference a prebuilt artifact and provide OS/architecture selection and compatibility checks.

Whichever contract is selected:

  • dependencies should be installed locally for the plugin and must not depend on system Python packages or globally installed packages;
  • refresh should reproduce or preserve the dependency closure;
  • a plugin should never become active when its declared runtime is incomplete;
  • installation failures should be explicit, not deferred until a hook exits 1;
  • the same installed bytes should remain runnable for the lifetime of sessions using them.

Existing issue search

Searched the open issue register for plugin dependency management, Python venv dependencies, package.json dependencies, requirements.txt, system Python, and plugin install lifecycle scripts. #18863 is related but narrower: it covers symlinks already present in source, not the absence of a dependency installation contract.

View original on GitHub ↗