fix(plugin): ship skills to Codex installs via a generated real-file mirror - #3440
Conversation
…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
left a comment
There was a problem hiding this comment.
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.
Codex installs of the worktrunk plugin carried no skills.
codex plugin addcopies the plugin into$CODEX_HOME/plugins/cache/viacopy_dir_recursive(codex-rs core-plugins), which handles only regular files and directories — so theskills -> ../../skillssymlink (and the nestedreference/README.mdlink 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 scratchCODEX_HOME: the installed root had noskills/at all andcodex debug prompt-inputshowed an empty plugin skill inventory.This PR cuts the plugin's
skillssymlink over to a generated real-file mirror of the authored repo-rootskills/, kept current by a newsync_plugin_skills_mirrorstage intest_docs_are_in_sync(dereferences symlinks, deletes stale files, self-heals and fails on drift — same pattern as the other generated mirrors). Repo-rootskills/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_rootsincodex-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:
plugins/worktrunk/skills/are the generated mirror (byte-identical to repo-rootskills/; 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_mirrorintests/integration_tests/readme_sync.rsis the sync stage (Step 3b of the pipeline); it already proved itself once in this branch — merging main regeneratedreference/{config,list}.mdin the mirror.test_plugin_layout_is_consolidatednow pins the mirror shape cross-platform (real directory, no symlinks anywhere under it), replacing the unix-only symlink assertion.Verification: fresh scratch-
CODEX_HOMEinstall now carries both skills into the cache andcodex debug prompt-inputlistsworktrunkandwt-switch-create;claude plugin validatepasses andclaude --plugin-dir … plugin detailsdiscovers 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.