Conversation
…n-only discovery Move the Claude manifest to plugins/worktrunk/.claude-plugin/plugin.json (the only location `claude plugin validate` accepts) and drop its `hooks`/`skills` keys: the string-path hooks override is not honored for plugin loads (#3417) and skills are auto-discovered from skills/, so both keys were dead or redundant config. Update the layout docs and invert test_plugin_layout_is_consolidated to pin the new invariants. Verified against claude-cli 2.1.207: a scratch marketplace mirroring this exact shape installs, loads both skills, and fires the hooks; the prior root-level manifest loads only via an undocumented fallback and fails validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
worktrunk-bot
approved these changes
Jul 12, 2026
max-sixty
added a commit
that referenced
this pull request
Jul 12, 2026
…eld (#3434) Follow-up to #3431: records in `plugins/worktrunk/CLAUDE.md` why the Claude manifest deliberately omits a `version` field — installs pin the marketplace git SHA, so a semver would be a second version to maintain with nothing consuming it — so a future cleanup pass doesn't "fix" the `claude plugin validate` warning by adding one. > _This was written by Claude Code on behalf of max_ Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
max-sixty
added a commit
that referenced
this pull request
Jul 13, 2026
…mirror (#3440) 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_ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Moves the Claude plugin manifest to the canonical
plugins/worktrunk/.claude-plugin/plugin.jsonand strips it to metadata only, dropping thehooksandskillskeys so that both component types load purely by Claude Code's convention discovery. Rewrites the layout docs and invertstest_plugin_layout_is_consolidatedto pin the new invariants.Background
The plugin layout has been in flux: #3382 Claude-scoped the hooks filename, which silently broke hook loading (#3417), and #3418 reverted it. That revert restored working hooks but left two claims baked into the layout, the docs, and the test that this PR found to be stale.
Research method
Everything below was verified empirically against claude-cli 2.1.207 (the current release), not from docs alone:
.claude-plugin/marketplace.json, plugin in a./plugins/<name>subdir source,skillsas a symlink pointing outside the plugin dir, hooks at the conventional path) with two plugin variants — manifest in.claude-plugin/plugin.jsonvs. bareplugin.jsonat the plugin root — installed through the realclaude plugin marketplace add+claude plugin installflow.claude plugin detailsandclaude plugin validateon both variants.claude -pruns confirming skills appear in-session and hook commands actually execute (marker files, and reading the 🤖 activity marker from inside a live session).Findings
.claude-plugin/wrapper" claim no longer holds. The repo documented — and the test pinned — that Claude wantsplugin.jsonbare at the plugin root, and that a.claude-plugin/wrapper in a subdir-source plugin fails with "Plugin not found". On 2.1.207 the wrapper form installs, loads skills, and fires hooks end-to-end. The loader tries.claude-plugin/plugin.jsonfirst with rootplugin.jsononly as a runtime fallback, several marketplace-resolution paths read only the wrapper, andclaude plugin validaterejects the root-level form outright ("No manifest found in directory. Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json").skills/*/SKILL.md; askillsmanifest array only adds directories to that scan (entries equal to the default dir are filtered). An unlisted skill behind the out-of-plugin symlink loaded fine — the installer dereferences the symlink into a real directory in the install cache. Dropping the array also makes the old test guard ("every skill must be listed or it's silently invisible") structurally unnecessary: a new repo-root skill now ships to Claude, Codex, and Gemini with no manifest change anywhere.hooksmanifest key was dead config. Claude plugin hooks silently stop loading after hooks.json → claude-hooks.json rename (#2677f05) #3417 established the string-path override isn't honored for plugin loads; convention discovery ofhooks/hooks.jsonis the mechanism that works. A key pointing at the conventional path adds nothing and could mask a future mislocated file, so it's gone (one mechanism per guarantee).Changes
plugins/worktrunk/plugin.json→plugins/worktrunk/.claude-plugin/plugin.json; content reduced toname/description/author.plugins/worktrunk/CLAUDE.md: layout diagram and Claude path-resolution bullet rewritten to the convention-only model; the refuted wrapper claim and the stale no-auto-discovery claim removed.CLAUDE.md: the Plugin Layout pointer updated (dropped the stale "Codex no-hooks re-enablement" phrase — Codex hooks shipped inline in feat(plugin): ship Codex-native activity hooks #3364).test_plugin_layout_is_consolidated: now asserts the wrapper manifest exists, a root-levelplugin.jsondoes not, the manifest carries nohooks/skillskeys, the pluginskillssymlink resolves to the repo-rootskills/(unix only — Windows checkouts withoutcore.symlinksmaterialize it as a file), and every repo-root skill dir carries aSKILL.md(the scan silently ignores a dir without one).Codex and Gemini are untouched: Codex reads only its
.codex-plugin/wrapper, and its inlinehooksobject still overrides Codex's own convention discovery, so the #3362 collision stays closed independent of any of this.Verification
claude plugin validate plugins/worktrunkpasses (it failed on the old layout). Remaining warnings are deliberate: noversionfield (installs pin the git SHA as the version; a hand-maintained semver would go stale) and the plugin-rootCLAUDE.md(developer docs, not plugin context).claude --plugin-dirsession on this exact tree loads both skills and theUserPromptSubmithook sets the 🤖 marker, observed from inside the session.Risk
The one scenario this could regress is a Claude Code version old enough to read only the root-level manifest. That couldn't be tested directly; the mitigations are that the wrapper is the documented convention, the loader prefers it today, and Claude Code self-updates aggressively. The old layout's "verified end-to-end" claim most likely dates from an earlier 2.1.x behavior or a misdiagnosed failure.