Skip to content

fix(plugin): ship skills to Codex installs via a generated real-file mirror - #3440

Merged
max-sixty merged 4 commits into
mainfrom
codex-skills-discovery
Jul 13, 2026
Merged

fix(plugin): ship skills to Codex installs via a generated real-file mirror#3440
max-sixty merged 4 commits into
mainfrom
codex-skills-discovery

Conversation

@max-sixty

Copy link
Copy Markdown
Owner

Codex installs of the worktrunk plugin carried no skills. codex plugin add copies the plugin into $CODEX_HOME/plugins/cache/ via copy_dir_recursive (codex-rs core-plugins), which handles only regular files and directories — so the skills -> ../../skills symlink (and the nested reference/README.md link inside the tree) were silently dropped, and sessions load from that cache copy. Verified against codex-cli 0.144.1 both by reading the tagged sources and by installing this repo's plugin into a scratch CODEX_HOME: the installed root had no skills/ at all and codex debug prompt-input showed an empty plugin skill inventory.

This PR cuts the plugin's skills symlink over to a generated real-file mirror of the authored repo-root skills/, kept current by a new sync_plugin_skills_mirror stage in test_docs_are_in_sync (dereferences symlinks, deletes stale files, self-heals and fails on drift — same pattern as the other generated mirrors). Repo-root skills/ stays the authored home: Gemini hard-probes it at the extension root, the docs sync writes into it, and Windows checkouts read it. Reversing the symlink direction instead would put a symlink at the repo root, breaking Gemini's install copy and every Windows checkout — which also surfaces a latent bug this fixes: symlinks materialize as plain text files on Windows clones, so installs from a Windows checkout shipped no skills to Claude or Codex either.

It also drops the Codex manifest's skills: "./skills/" key: with no key, Codex scans <plugin-root>/skills/ by convention (default_skill_roots in codex-rs/core-plugins/src/loader.rs), and the explicit path resolves to the same directory, so the key was redundant — symmetric with the Claude manifest cutover in #3431.

For the reviewer:

  • The 20 files under plugins/worktrunk/skills/ are the generated mirror (byte-identical to repo-root skills/; git stores shared blobs once). plugins/worktrunk/CLAUDE.md → "Plugin skills are a generated mirror" documents the rationale with codex-rs citations.
  • sync_plugin_skills_mirror in tests/integration_tests/readme_sync.rs is the sync stage (Step 3b of the pipeline); it already proved itself once in this branch — merging main regenerated reference/{config,list}.md in the mirror.
  • test_plugin_layout_is_consolidated now pins the mirror shape cross-platform (real directory, no symlinks anywhere under it), replacing the unix-only symlink assertion.

Verification: fresh scratch-CODEX_HOME install now carries both skills into the cache and codex debug prompt-input lists worktrunk and wt-switch-create; claude plugin validate passes and claude --plugin-dir … plugin details discovers Skills (2) plus all 8 hooks; Gemini's repo-root path is untouched. A 2×2 probe matrix (manifest key present/absent × real dir/symlink) confirmed key-absence changes nothing and symlinks ship nothing.

This was written by Claude Code on behalf of max

max-sixty and others added 4 commits July 12, 2026 13:11
…the no-skills gap

Verified against codex-cli 0.144.1 (code reading + scratch-marketplace
probes through the real install flow):

- Codex discovers plugin skills by convention: an empty/absent manifest
  `skills` falls back to <plugin-root>/skills/ (plugin_skill_roots /
  default_skill_roots in codex-rs/core-plugins/src/loader.rs), and an
  explicit "./skills/" resolves to the same directory via a pure string
  join (resolve_manifest_path). The key was redundant; drop it for
  symmetry with the Claude manifest cutover (#3431).

- Neither form ships skills to a Codex install today: `codex plugin add`
  copies the plugin into $CODEX_HOME/plugins/cache/ via
  copy_dir_recursive (codex-rs/core-plugins/src/store.rs), which handles
  only regular files and directories — the plugins/worktrunk/skills
  symlink is silently dropped, and sessions load from that cache copy.
  Probe matrix (key × real-dir/symlink): a real skills/ dir loads
  identically with and without the key; the symlinked layout loads
  nothing either way, confirmed with `codex debug prompt-input` against
  this repo's actual plugin. Documented as a Known Limitation in
  plugins/worktrunk/CLAUDE.md; no manifest value can bridge it since
  manifest paths must stay within the plugin root.

Also fixes the repo-root skills/ diagram line (a real directory, drawn
as a symlink) and the stale "ships only the configuration skill" test
comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex installs carried no skills: `codex plugin add` copies the plugin
into $CODEX_HOME/plugins/cache/ via copy_dir_recursive (codex-rs
core-plugins store.rs), which handles only regular files and
directories — the `skills -> ../../skills` symlink (and the nested
`reference/README.md -> ../../../README.md` link) were silently
dropped, and sessions load from that cache copy. Verified against
codex-cli 0.144.1: installing this repo's plugin produced an empty
skill inventory in `codex debug prompt-input`.

Cut the symlink over to a real-file mirror of the authored repo-root
skills/, dereferenced and kept current (stale files deleted) by a new
sync_plugin_skills_mirror stage in test_docs_are_in_sync — the same
self-healing pattern as the other generated mirrors. Repo-root skills/
stays the authored home: Gemini reads it directly and the docs sync
writes into it. Reversing the symlink direction instead would put a
symlink at the repo root, breaking Gemini's install copy and every
Windows checkout (which materializes symlinks as plain text files — a
latent bug this also fixes: Windows clones shipped no skills to Claude
or Codex either).

Verified end-to-end: a fresh scratch-CODEX_HOME install now carries
both skills into the cache and `codex debug prompt-input` lists
worktrunk and wt-switch-create; `claude plugin validate` passes and
`claude --plugin-dir … plugin details` discovers Skills (2) and all 8
hooks from the real-dir layout.

test_plugin_layout_is_consolidated now pins the mirror shape — a real
directory with no symlinks anywhere under it — replacing the unix-only
symlink assertion (the invariant is now cross-platform).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good — the mirror correctly dereferences the nested reference/README.md symlink into byte-identical real files, the sync runs after sync_skill_files so it mirrors fresh content, and test_plugin_layout_is_consolidated catches a symlink before recursing into it (is_symlink() check precedes the is_dir() push). Verified the manifest-key drop and the codex-cli citations against the described behavior.

One non-blocking note on the "self-heals on drift" property: sync_plugin_skills_mirror writes before it removes, so a same-path type flip — a reference/foo.md file becoming a foo.md/ directory (or vice versa) — would panic on create_dir_all/write rather than converge, and wouldn't self-heal on rerun. Effectively unreachable in this all-.md tree and it fails loudly with the offending path, so not worth restructuring the loop over — just flagging since the design aims for self-healing.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants