Agent Skills: Copier Template Maintenance & Propagation

Maintain a Copier project template and propagate updates to generated ("descendant") repos. Covers template anatomy (copier.yml, jinja, tasks), testing template changes, tagging/releasing versions, the automated update-notification PR pattern, and applying copier update in descendants — bringing a repo fully to the latest template version while preserving its app behavior, with conflict resolution an autonomous agent can run end to end.

UncategorizedID: gallop-systems/claude-skills/copier-template

Install this agent skill to your local

pnpm dlx add-skill https://github.com/gallop-systems/agent-skills/tree/HEAD/plugins/copier-template/skills/copier-template

Skill Files

Browse the full folder contents for copier-template.

Download Skill

Loading file tree…

plugins/copier-template/skills/copier-template/SKILL.md

Skill Metadata

Name
copier-template
Description
Maintain a Copier project template and propagate updates to generated ("descendant") repos. Covers template anatomy (copier.yml, jinja, tasks), testing template changes, tagging/releasing versions, the automated update-notification PR pattern, and applying copier update in descendants — bringing a repo fully to the latest template version while preserving its app behavior, with conflict resolution an autonomous agent can run end to end.

Copier Template Maintenance & Propagation

Patterns for the full lifecycle of a Copier project template: authoring changes, testing them, releasing versions, and rolling updates out to every repo generated from the template.

When to Use This Skill

  • Editing the template repo (questions, scaffold files, tasks, CI)
  • "Upstream this pattern to the template" — porting something proven in a descendant
  • Tagging/releasing a new template version
  • Applying a template update in a descendant repo (often via an automated "template update available" PR)
  • Debugging copier copy/copier update failures

Mental Model

  • The template repo holds copier.yml (questions + settings + tasks) and a template/ subdirectory of scaffold files (some .jinja-suffixed for substitution). Git tags (v*) are the version protocol; GitHub Releases are the changelog protocol.
  • Each descendant carries .copier-answers.yml recording its answers and _commit: vX.Y.Z — the template version it's on. Never hand-edit this file; copier update maintains it.
  • copier update re-renders from old-tag → newest tag and three-way merges against local changes. It always jumps to the latest tag (unless --vcs-ref pins one) — a notification PR advertising v1.5.0 may actually land v1.8.0 if the template moved on. The job is never to bump a version string — it is to standardize whatever the template now sells while keeping this app's behavior intact. Hand-editing _commit or any version number to shrink a diff is always wrong; let copier update move it, and absorb the real changes.
  • Run copier via uvx copier ... (no global install needed). Templates with _tasks require --trust — without it copier refuses to render at all. Non-interactive contexts also need --defaults (and --data key=value for required questions without defaults).

Direction of Change

Prove patterns in a real descendant first, then upstream. Build and merge the feature in one generated project; once proven, port it into template/ with jinja-aware adaptations. The upstream PR body should cite the originating repo/PR and include validation evidence (a project generated from the branch passing typecheck/lint/tests). Exception: infra-only changes (CI jobs, hooks config) can go straight to the template. For risky changes, stage the rollout — hand-run script in one repo first, graduate to the template once the win is proven.

When working in a descendant and a fix belongs in the template too: fix the symptom locally and make the corresponding change in the template repo (verify you have the right repo with git remote -v — don't trust the directory name).

Releasing a Template Version

First determine how the template releases — it changes everything below:

ls release-please-config.json .release-please-manifest.json 2>/dev/null   # present ⇒ release-please

If the template uses release-please (e.g. nuxt-copier-template)

You never git tag by hand. Merging to main runs release-please, which opens a "release PR"; merging that cuts the tag + GitHub Release. The Conventional-Commits type of your merged PR governs the entire outcome — both the version bump and whether a release happens at all:

  • feat:minor bump. fix:patch. feat!: / BREAKING CHANGE:major.
  • chore:, docs:, refactor:, style:, test:, ci:, build:no version bump, no release. The change lands on main but sits in the (often hidden) "Miscellaneous" changelog bucket, invisible to descendants, until some later feat/fix rides out and drags it along.

The trap: a template change that should propagate — a new alias/convention, a raised dependency floor, anything descendants must adopt — is a feat (or fix), not a chore. Type it chore and it silently never releases; descendants track git tags, so no tag = no copier update PR. When in doubt about whether descendants need it, it's a feat.

If you already merged it as the wrong type (non-releasing), don't wait — force a release with an empty commit carrying a Release-As footer, via a normal PR (squash-merge it):

git commit --allow-empty -m "chore: release template <X.Y.Z>

<why this is being force-released>

Release-As: <X.Y.Z>"

release-please honors Release-As: regardless of commit types and opens the release PR at that exact version. Note: release PRs get no CI (the meta-test workflow doesn't run on release-please--branches--*), so if branch protection requires a status check, the release PR stays BLOCKED under a normal merge and an admin/maintainer must merge it — that's the expected path for release PRs here, not a failure.

If the template has no release automation (manual tags)

git checkout main && git pull --ff-only
git tag --sort=-v:refname | head -5          # see existing versions
git cat-file -t v<latest>                     # match the tag type convention (lightweight vs annotated)
git tag v<X.Y.Z> && git push origin v<X.Y.Z>
gh release create v<X.Y.Z> --title "v<X.Y.Z> — <summary>" --notes "$(cat <<'EOF'
## Changes
- ...

## Upgrading
Run `uvx copier update --trust --defaults` in your project.

Full diff: <template-repo-url>/compare/v<prev>...v<X.Y.Z>
EOF
)"
  • Semver: patch = fixes/dep bumps; minor = new features, questions, or components; major = breaking structure changes.
  • Always create the GitHub Release, not just the tag. Descendant notification PRs link to /releases/tag/<version> — a tag without a release produces dead links downstream.

Automated Update Notification

The template ships its descendants a checker workflow (daily cron + workflow_dispatch) that compares .copier-answers.yml's _commit against the template's highest remote tag:

git ls-remote --tags --refs --sort=-v:refname <template-url> 'v*' | head -1

If newer, it pushes a static branch name (e.g. chore/template-update) with an --allow-empty commit and opens a PR whose body contains the version delta, release-notes/compare links, and step-by-step instructions an agent can execute. Hard-won details to keep if reimplementing: an explicit permissions: contents: write, pull-requests: write block (default token can't open PRs), a static branch name (dated branches caused duplicate PRs), and comparing tag versions, not commit SHAs.

Dependency Updates: Who Owns What

Two Renovate instances run, with a deliberate boundary so they never fight:

  • The template's Renovate (in the template repo) keeps the pins in template/package.json.jinja fresh via a custom regex manager, and auto-merges @gallopsystems/agent-skills — which release-please then cuts as a template release. Those bumps reach descendants through copier update.
  • Each descendant's Renovate (shipped as renovate.json, gated on the include_renovate question) owns that repo's own app dependencies — the only place an upgrade can be tested against the real app's code and CI.

The one overlap is resolved by ownership: the descendant's renovate.json disables @gallopsystems/agent-skills, leaving it solely template-owned. Every other pin is the descendant's. Because the template keeps bumping all pins, a descendant's package.json arrives with version conflicts on copier update — resolve them by keeping the descendant's versions (see applying-updates.mdpackage.json dependency pins). Newly-scaffolded repos start on the template's pins and are freshened by their own Renovate within a day.

Branch Protection in Descendants

A template cannot enable branch protection for the repos it generates — GitHub reads required status checks from repo config, never from committed workflow files. So every descendant starts with nothing gating merges until someone sets it once (after the first CI run, so the check is known). This template's CI exposes a ci-success summary job to be exactly that gate — require it on main:

echo '{"required_status_checks":{"strict":false,"contexts":["ci-success"]},"enforce_admins":false,"required_pull_request_reviews":null,"restrictions":null}' \
  | gh api -X PUT repos/<owner>/<repo>/branches/main/protection --input -
  • Require the single ci-success context, not individual job names. CI shards the test suite, so the per-leg check names embed the matrix size (test (shard 1/4)) and change as the project grows — protection pinned to them blocks every PR the moment the count shifts. ci-success is a summary job (needs: all gating jobs, if: always(), fails unless every needs.*.result is success); needs.test.result rolls the whole matrix into one value, so it stays correct at any shard count. If you add a template whose CI lacks such a job, create one rather than requiring the matrix legs directly.
  • coverage is deliberately excluded from ci-success so the report never blocks a merge — don't add it to the required contexts.
  • The PUT body must include required_status_checks, enforce_admins, required_pull_request_reviews, and restrictions (any may be null) or the call 422s. Needs admin on the repo.
  • A Conventional-Commits PR-title check is only worth requiring on repos something actually reads the title — i.e. a published package with release-please/changelog automation. A private app (no release tooling, "private": true, no version) gains nothing from it; don't gate on it there.

Further Reading

  • Template anatomy & testing changes: template-authoring.md
  • Applying an update in a descendant (the full procedure — read-the-changes-first, multi-version deltas, .rej triage, silent-overwrite review, and autonomous merge-readiness): applying-updates.md

Contributing Back

This skill grows by capturing what it missed. If you just worked through something in this domain that this skill did not cover — an error you had to figure out, a behavior that contradicts what is documented above, a workflow knot — ask the user: "Want me to contribute this back to the copier-template skill?"

If yes, run /contribute-skill. If that command is not available, do the equivalent inline: distill the generic lesson (placeholders only — no project names, IDs, domains, or secrets), then branch or fork gallop-systems/agent-skills and open a PR editing this skill.