name: regenerating-patches description: Regenerates plugin-cache patches in scripts/fleet/plugin-patches/ against the pinned upstream plugin source when they go stale after a plugin SHA bump. Use when install-claude-plugins.mts warns that a patch no longer applies, or after bumping a plugin's source.sha in marketplace.json. user-invocable: true allowed-tools: Read, Edit, Write, Glob, Grep, Bash(node:), Bash(patch:), Bash(diff:), Bash(git:), Bash(mkdir:), Bash(rm:), Bash(cat:), Bash(ls:), AskUserQuestion model: claude-haiku-4-5 context: fork
regenerating-patches
Regenerate the wheelhouse-owned plugin-cache patches in scripts/fleet/plugin-patches/ so each one applies cleanly to the currently pinned upstream plugin source. This is the recovery flow when a plugin's source.sha bumps in .claude-plugin/marketplace.json and the line numbers shift under our patches.
Patches are reapplied over the plugin cache by scripts/install-claude-plugins.mts (reapplyPluginPatches() → patch -p1). The cache is regenerated from the pinned source on every install, so a stale patch warns and no-ops — it never wedges the reconcile, but the bug it fixed reappears until the patch is regenerated.
The authority on the patch format is docs/agents.md/fleet/plugin-cache-patches.md. The edit-time gate is .claude/hooks/fleet/plugin-patch-format-guard/. The pin-reading, fetch (httpText), classify (patch --dry-run forward + reverse), diff-rebuild (timestamp strip + path rewrite), header restamp, and final validate plumbing all live in lib/regen-patches.mts. The skill keeps only the AI judgment — re-applying each stale patch's intent to the new pristine source with the Edit tool.
Phase 1 — classify
# Print {current|upstreamed|stale} per patch, against the currently pinned SHA.
node .claude/skills/fleet/regenerating-patches/lib/regen-patches.mts classify
current— applies cleanly; leave it.upstreamed— the fix is already in the pinned source (forward fails, reverse applies); flag forrm+ manifest-entry removal, not regeneration.stale— applies neither way; regenerate it (Phase 2).
The script reads .claude-plugin/marketplace.json itself and fetches pristine sources via the lib httpText from the pinned-SHA raw URL. Surface any fetch / parse error and stop rather than guessing.
Phase 2 — regenerate the stale patches
For each stale patch:
- Stage the pristine target file(s) at the pinned SHA into a temp
a/tree:node .claude/skills/fleet/regenerating-patches/lib/regen-patches.mts pristine <name>.patch # prints the staging dir; copy a/ → b/: cp -R <dir>/a <dir>/b - Read the stale patch to recover its intent (the
+/−lines and which files they touch). - Re-apply that intent to the
b/<file>copy with the Edit tool — exact-match editing forces byte-for-byte context against the new pristine source. (This step is yours; the script does the plumbing around it.) - Rebuild + restamp + validate in one call — emits the clean
diff -ubody (timestamps stripped, paths rewritten toa/-b/) under the restamped# @header, and fails non-zero unlesspatch -p1 --dry-runexits 0:
If the version changed, rename the file and bumpnode .claude/skills/fleet/regenerating-patches/lib/regen-patches.mts rebuild <name>.patch <dir> \ > scripts/fleet/plugin-patches/<name>.patch# @plugin-version:in the header after writing.
Phase 3 — report
Print three lists and stop. Don't commit, don't push — the user reviews the regenerated patches first.
regenerated: patch basenames rewritten against the new pin.unchanged: patches that already applied.deleted/upstreamed: patches whose fix is now in the pinned source (flagged forrm+ manifest-entry removal — the bug is fixed upstream).unrecoverable: patches the regen couldn't fix + the diagnostic.
Smallest footprint — prefer a sidecar over inlining
When the fix is more than a few lines, move the logic into a standalone module and let the diff just import it + swap call sites. Ship the module in the patch's companion <x>.files/ dir (tree mirrors the cache root); reapplyPluginPatches() copies it in before applying the diff. A thin diff re-anchors across version bumps; a fat inlined one breaks on the first nearby edit. When regenerating, keep the diff thin — don't re-inline a body that already lives in .files/. (Exception: targets that can't import a sibling we control, e.g. some pnpm patch cases — inline there.)
Patch format
A # @key: value provenance header above a plain diff -u body. Filename <plugin>-<version>-<slug>.patch (dotted semver version); substantial logic lives in the companion <x>.files/ sidecar, not the diff. Authority: docs/agents.md/fleet/plugin-cache-patches.md.
# @plugin: codex
# @plugin-version: 1.0.1
# @sha: 9cb4fe4099195b2587c402117a3efce6ab5aac78
# @upstream: https://github.com/openai/codex-plugin-cc
# @description: One-line summary of what the patch fixes
#
--- a/scripts/lib/fs.mjs
+++ b/scripts/lib/fs.mjs
@@ -32,9 +32,39 @@
context
-old
+new
context
Required header keys: @plugin, @plugin-version, @sha, @description. @upstream is recommended.
Constraints
- Use
diff -u, nevergit diff/git format-patch. Both inject git markers (diff --git,index <hash>..<hash>,new file mode) thatpatch -p1doesn't expect — and theplugin-patch-format-guardhook rejects them at edit time. - Strip timestamps from the
---/+++lines:grep -v $'^[-+]\{3\}.*\t'.diff -uadds them;patchchokes on them. - The apply tool is
patch -p1— the same toolinstall-claude-plugins.mtsuses. The-p1strips the leadinga//b/segment, so paths are plugin-root-relative. - Don't commit or push. The user reviews the regenerated patches before committing.
- Fetch from the pinned SHA, never a branch.
raw.githubusercontent.com/<owner>/<repo>/<sha>/…— a branch URL drifts and would regenerate against the wrong source.