manage-versions
Skill2 starsChanged 5 days agoMITView on GitHub
Keep cross-repo changes version-compatible through development, releases, and consumer pins. Load when repos must remain compatible to merge: unreleased dependencies, proto/REST contract changes, or breaking published symbols.
- Installs packages
- Commits and pushes
In this repository40 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .agents/skills/AGENTS.mdAGENTS.md
- attribute-ai-commitsSkill
- check-accessSkill
- choose-dependencySkill
- coordinate-landingSkill
- develop-featureSkill
- document-codeSkill
- git-conventionsSkill
- implement-featureSkill
- monitor-ciSkill
- open-pull-requestSkill
- plan-client-server-boundarySkill
- plan-featureSkill
- plan-ui-designSkill
- prefer-small-solutionsSkill
- prepare-releaseSkill
- register-ai-agentSkill
- resolve-pr-feedbackSkill
- triage-issuesSkill
- use-a-novel-cliSkill
- use-discordSkill
- write-bash-scriptsSkill
- write-design-systemSkill
- write-dockerfilesSkill
- write-frontendSkill
- write-frontend-testsSkill
- write-github-actionsSkill
- write-go-kitSkill
- write-go-serviceSkill
- write-goSkill
- write-go-testsSkill
- write-infraSkill
- write-js-packageSkill
- write-openapiSkill
- write-platformSkill
- write-project-docsSkill
- write-protoSkill
- write-sqlSkill
- write-svelteSkill
The file
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 5 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 11
Every repo in a-novel and a-novel-kit is independently versioned, and the repos depend on each other (services pin golib, jwt, each other's …
- Very specific: line 35
Renovate keeps consumers current: After a dependency releases, Renovate opens a PR in each consumer bumping the pin and running the …
- Distinctive: line 146
Every SHA must be on the default branch, or the run fails before creating anything.
- Distinctive: line 164
Security hotfix (embargoed): When the fix can't be public before release:
- Distinctive: line 234
Fix a red bump PR on its own branch: When the release needs a code migration, such as a visual change a consumer's tests pin, push only …
- Very specific: line 251
Add the new versions next to the old ones, then run pnpm install.
- Very specific: line 252
Remove the old entries, then pass pnpm install --frozen-lockfile.
- Very specific: line 254
Never hand-merge pnpm-lock.yaml during a rebase.
- Distinctive: line 259
If B's change is itself a dependency of a repo C, repeat: A → release → Renovate bumps B → B releases → Renovate bumps C → C merges.
- Very specific: line 329
Search both app/ (services) and kit/ (other shared libraries) — a graduated-package change rips through jwt consumers as easily as service …
- Very specific: line 369
Use the BREAKING CHANGE: commit footer (git-conventions) only on step 3 — the actual removal — not on step 1.
- Distinctive: line 376
When a change spans repos:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Cross-repo version management
Every repo in a-novel and a-novel-kit is independently versioned, and the repos depend on each
other (services pin golib, jwt, each other's pkg/go; deployments pin each other's Docker
images; clients pin published packages). When a change touches more than one repo, the versions
must line up before anything merges to production. This skill governs that.
Load it with whichever repo-kind skill applies (write-go-kit for a kit/ change,
write-go-service for a service change), with git-conventions (the breaking-change commit
footer) and implement-feature (the layered-branch workflow these cross-repo plans extend).
How things are versioned
- Each repo is git-tag semver. A release is a
vX.Y.Zgit tag pushed on amastercommit; the next version comes from the commit history since the last tag (fix:/chore:→ patch,feat:or an absorbable breaking change → minor). A!does not force a major here: a major is a plannedvX-line initiative, never derived from a commit range —prepare-releaseowns that policy and the sizing. The tag push triggers thereleaseworkflow (a-novel-kit/workflows/publish-actions/auto-release), which cuts the GitHub Release and, for service repos, builds and publishes the Docker images / npm packages tagged with that version. go.modpins exact released tags.github.com/a-novel-kit/golib v0.20.31, not a range, not a branch. Noreplacedirectives anywhere in this codebase — the dependency graph stays reproducible fromgo.modalone.- Renovate keeps consumers current. After a dependency releases, Renovate opens a PR in each
consumer bumping the pin and running the configured post-upgrade tasks (
go mod tidy, regenerate proto, …). A released version reaches a consumer'smasteronly through Renovate, a feature branch waiting on it included, as bring a release through Renovate shows. Only a draft's pseudo-version pin, below, is ever written by hand. Production is the exception: a service or platform release pins its own images in infra, through infra'spin-release.yaml. - Docker-image pins in CI / compose files follow the same rule: pin a concrete
vX.Y.Z(or${{ github.ref_name }}for the repo's own image), neverlatestor a branch.
Cutting a release
A release is cut in CI, not from a developer's working tree. An admin triggers the repo's
release workflow and picks a release type (patch / minor / major), or the release train does
(coordinate-landing). release.yaml then runs in three parts (a-novel/.github#414):
planruns therelease-coreaction as a dry run, with no environment and no credential: it computes the version and checks the bump, the doc stamp and the tags. A rehearsal runs only this.cutrunsrelease-corein the protectedreleaseenvironment as the release App, whose key only that environment holds. It bumps version files (workspace-aware), refreshes doc refs (prepublish:doc/a-novel publish stamp), commits, tagsvX.Y.Z, pushes the commit and tags together, and creates the GitHub Release.- The tag publishes. The root tag runs
release.yamlagain, and that run builds and attests the images, attests and publishes the npm tarballs, and dispatches infra'spin-release.yaml.
A repository that has not adopted the split yet releases in one job as the [Agent] bot; the
workflows repo's docs/migrations/v1.57.0.md is the adoption guide.
There is no local release command, and no per-repo publish.sh or publish:* pnpm script —
do not write or revive any of them. The bump is workspace-aware: release-core detects whether the
repo is a pnpm workspace (a pnpm-workspace.yaml is present) and runs the matching pnpm version
form — --recursive for workspaces, --no-git-tag-version for single-package repos. The only verb
under a-novel publish is stamp, the doc-version helper prepublish:doc calls (see
use-a-novel-cli).
Who can cut a release is enforced server-side, by rulesets and the environment that a-novel/infra's GitHub root manages:
- The
masterruleset restricts who can push the version-bump commit. - The
tagsruleset restricts who can create a tag. A tag is the release decision: it starts the publishing run, so only admins and the release bot may create one. - The
releaseenvironment holds the release App's key and admits only the default branch,release/v*lines andv*tags. Every cut waits there forcoreor the repo'sowners-<class>team; the person who dispatched may approve their own run. The first backport onto a new line waits there too, since only the release App may create the line.
These guard the release, never the registries. GitHub Packages lets every workflow run of a
repository write that repository's packages, whatever its ref, so a branch can publish an npm version
or push and move an image tag. A version proves its origin only through its release proof: an
attestation over its bytes, signed by the producer's release.yaml at the version's tag, and an
immutable Release of that tag from the release App. A branch run's attestation names its branch, and
only a release makes the tag and the Release. The auto-approver merges a first-party version only
with that proof (write-github-actions), and infra deploys an image only with it (write-infra).
A v* tag is only ever created by a CI dispatch — the release or release-train job, running as the
release App. Nobody pushes one by hand, maintainers included. Repo admins can bypass every rule
here, but that power exists to repair a broken state, never to cut a release. GitHub starts no
workflow when more than three tags arrive in one push, and the cut pushes the root tag with one tag
per Go sub-module, so a repository keeps fewer than three sub-modules.
Releases are human-triggered but bot-run. A human starts the release workflow; the agent never decides to publish (or merge, or push to master) on its own, and a non-interactive token must lack those rights server-side.
Hotfixing a released line
A bug sometimes must be fixed on an already-released line without shipping whatever has since
landed on the default branch. That is a hotfix. The fix lands on the default branch first,
through its normal PR. A backport then carries it onto the released line, and merging the
backport cuts the patch. The default branch is never rewritten and never receives a backport. A
hotfix is a single-repo path; a bug spanning several repos is a fast Epic (see
coordinate-landing).
Nothing rolls a release back. Infra refuses a pin that lowers a version, so a bad release is fixed forward, and the hotfix is the fast path:
- When the default branch holds nothing unreleased since the deployed release, skip the hotfix: a normal patch release ships the fix with one review less.
- A hotfix deploys only on the deployed line or a newer one. A patch to an older line than the one infra pins reads as a lowered version, and infra refuses it.
- A pending newer release swallows a hotfix's pin.
pin-release.yamlkeeps one pin pull request per repository at the newest release it saw. While a newer release's pin is still open, the hotfix folds into it and never deploys alone: close that pull request first.
Vocabulary (single-repo hotfix — freeze it here):
- baseline — the latest live tag on the target
X.Yline (e.g.v1.4.2for line1.4). - release line —
release/vX.Y, created from the baseline the first time the line needs a fix. It holds released code plus backports only, and its head is always its latest patch. Therelease-linesruleset protects it like the default branch: a PR, one approval, squash, and master's required checks. Only admins and the release bot write to it directly. - backport — a PR into a release line carrying a fix in the form that line's code needs, titled
<fix subject> [vX.Y]. Its checks run against the released code, so a fix that leans on unreleased code fails before it ships. - cut — merging a backport runs
release-core-hotfixbehind the protectedreleaseenvironment, as the release App. It bumps the patch, then pushes the bump commit to the line together with thevX.Y.(Z+1)tag. It creates the Release with--latest=false, so a patch to an old line never becomes "Latest". The tag runs the line's own publishing jobs, so the patch reaches production, except on a line cut before its repository adopted the release split. - retract — for a Go module, a
retract vX.Y.Zdirective (added in a later release) marks a bad version sogo getskips it. The tag stays (immutable);retractis the downstream "don't use this".
Ship a hotfix.
- Land the fix on the default branch through its normal PR.
- Dispatch the repo's
hotfix.yamlfrom the UI. Only admins may run it, anddry_rundefaults on. Set theX.Yline andfix_refs: the fix's default-branch commit SHAs, oldest first. Every SHA must be on the default branch, or the run fails before creating anything. Each fix becomes its own backport PR. An emptyfix_refsonly prepares the line, for a fix the default branch does not need: branch fromrelease/vX.Yand open that backport by hand. - A fix that does not apply cleanly opens nothing; a typical cause is a rename on the default branch
that the line lacks. The escalation ticket carries the commands to write the line's form of the fix
by hand: cherry-pick, resolve the conflicts against the released code, and open the PR into
release/vX.Y. - A clean backport, whose patch-id equals its fix's on the default branch, is opened by the
Dependencies App, approved by the agent and merged once its checks pass: it carries the fix's
review. A backport that git fitted to different context opens as the agent: review and merge
it. The merge cuts the patch once a
releaseenvironment reviewer approves. Backports merged together cut one patch.
A backport never receives a required check that was added after its line's tag, so an admin merges
past that check. Nothing reminds anyone of a backport left open, so merge it once it is approved. A
hotfix is deliberately exempt from AGENT_KILL_SWITCH, so the emergency patch path stays open even when
normal automation is halted. The admin check and the release environment gate it instead.
Security hotfix (embargoed). When the fix can't be public before release:
- Work it under a draft GHSA advisory in a temporary private fork. A private fork inherits none of the org secrets or runners, so there is no CI there and nothing leaks.
- Coordinate the disclosure.
- Land the fix and its backport back to back, cut the patch, and publish the advisory at the same time.
retractthe vulnerable version(s) in the follow-up.
The default branch carries the fix before the patch exists, so no later release can re-ship the
hole. Outward policy (contact, disclosure window) lives in SECURITY.md; the mechanics live here.
Working a cross-repo change on a branch
You're changing repo A (the dependency — e.g. golib) and repo B (the consumer — e.g. a
service) together, and B's branch needs A's unreleased code. Don't add a replace; pin B to A's
commit SHA, which Go records as a pseudo-version:
# In B, on B's branch, after pushing A's branch:
go get github.com/a-novel-kit/golib@<commit-sha-on-A's-branch>
go mod tidy
# go.mod now reads e.g. github.com/a-novel-kit/golib v0.20.32-0.20260512140000-abcdef123456- Pin to an explicit commit SHA, not
@<branch-name>— a branch ref resolves to whatever the tip is at the moment you rungo getand can silently drift; an SHA is stable. (go get @mainalso produces a pseudo-version, just a less reproducible one.) - Each time you push more commits to A's branch, re-run
go get …@<new-sha>+go mod tidyin B so B builds against the latest A. - B's CI goes red on a pseudo-version pin where the org enforces "released versions only", as it should: that is the signal B is not yet mergeable. See the ordering rule.
- Only a draft pins a pseudo-version. A ready PR never pins an internal library to a commit,
since a reviewer may approve and merge it. Before marking B ready, ask the developer to cut A's
release, bring it to B's
masterthrough Renovate, and drop the pseudo-version commit as B rebases. Do not mark B ready on the pseudo-version and ask afterwards.
The merge order — dependency first, always
A pseudo-version points at a commit on a branch. Whether that commit survives on master depends on
the merge strategy — a merge commit keeps it, a squash- or rebase-merge does not, and once the
branch is deleted GitHub serves the orphaned commit only for a while. Either way, a PR that merges
with a pseudo-version pin bakes a non-release into production: unreproducible from tags, invisible
to Renovate, and a landmine once the source commit is gone. A pseudo-version pin is safe only
during in-flight development, never at merge time. So:
Hard rule: the dependency PR merges and releases before the consumer PR merges, and the release
reaches the consumer's master through Renovate first.
- Merge A's PR (the dependency change) to A's
master. The release workflow tagsvX.Y.Zand publishes it. - Bring the release to B's
masterthrough Renovate, as the next section describes. - Rebase B's branch on
masterand drop its pseudo-version commit. B's diff now holds only the feature, and its CI goes green on the released version. - Merge B's PR. Now production is fully on released versions.
Bring a release through Renovate
A feature branch never pins a release itself. Renovate merges bumps on every consumer all day, so a
pin commit in a feature branch conflicts with each one that touches the same lines. It also asks the
feature's reviewer to acknowledge a dependency change, with allow-dependency-change, and mixes a
version bump into a feature diff.
- Dispatch Renovate in each consumer once the release exists:
gh workflow run renovate.yaml --repo <org>/<consumer>. Wait for its bump PR. - Let a green bump PR land. Its auto-merge queues it.
- Fix a red bump PR on its own branch. When the release needs a code migration, such as a visual change a consumer's tests pin, push only that migration onto Renovate's branch, right after its last rebase. The push withholds the auto-approval, so the PR needs a maintainer's approval. Keep everything else for the feature branch.
- Rebase the feature branch on
masteronce every bump has merged. It carries the leftovers: the feature, without a pin or a lockfile change. A rebase conflict with the migration means the feature repeated it; keepmaster's side.
It takes longer than a pin in the feature branch, but nothing conflicts, the feature needs no dependency label, and each review reads one kind of change.
For a pnpm consumer, a fresh first-party release installs at once: every workspace exempts the
@a-novel and @a-novel-kit scopes from pnpm's minimumReleaseAge, since the release proof stands
in for the wait. A fresh third-party release sits inside that age (one day by default). Exempt it by
exact version in pnpm-workspace.yaml minimumReleaseAgeExclude, one entry per pinned package. Bump
in two steps, because pnpm also rejects the outgoing pin when it is still too fresh:
- Add the new versions next to the old ones, then run
pnpm install. - Remove the old entries, then pass
pnpm install --frozen-lockfile.
Never hand-merge pnpm-lock.yaml during a rebase. At each conflicting commit, take the upstream
lockfile (git checkout --ours), then run pnpm install. Exempt only the versions pnpm names, and
only while installing. Restore the commit's workspace file, and pass --frozen-lockfile before
continuing. The policy relaxation never reaches a commit.
If B's change is itself a dependency of a repo C, repeat: A → release → Renovate bumps B → B releases → Renovate bumps C → C merges. Stacked dependency chains merge bottom-up.
Write the order down so it can't get lost — in B's PR description, and/or as TaskCreate
entries:
1. golib#NNN (adds X) → merge → release v0.20.32
2. service-foo#MMM (uses X) → Renovate bumps golib to v0.20.32, rebase, then merge
3. (if needed) golib#PPP (removes old X) → after #2 ships → release v0.21.0Edge case — A is
golib, which has no separate "release PR" and releases on tag: dispatch A's release workflow once its PR merges. Don't merge B until that tag exists and B is pinned to it. If you cannot dispatch it, hand off: "golib#NNN is merged; needs a release tag before service-foo#MMM can land: dispatch Renovate there oncevX.Y.Zis out."
Breaking changes are staged — never broken in one shot
A breaking change to anything other repos depend on — a golib / graduated-package public symbol,
a service's pkg/go API, a proto message, a REST endpoint, a DB schema another reader relies on —
is never a single PR that flips old → new. Stage it across releases, so there is always a window
where both old and new work.
Step 0 — Grep the whole workspace for consumers first
Breaking changes are fine when genuinely needed — a better API, a security fix that required a signature change, an obsolete helper a newer dependency now covers. The discipline this skill enforces is knowing the blast radius before you start, so the consumer-migration step isn't a scramble.
Before writing the additive PR, enumerate every consumer of the symbol you're about to change. That grep:
- Tells you the blast radius — one consumer is a different change than ten, and the survey costs the same either way.
- Seeds the consumer-migration list in step 2 below, tracked explicitly so no consumer is forgotten and fails silently at step 3 (removal).
- Surfaces the case where the "shared" symbol has zero in-org consumers, and lets you decide what
that means for this particular symbol. For an internal / not-yet-public helper (a fresh
golibsub-package no service imports yet, a service's privatepkg/gosymbol), zero consumers usually means the additive-then-deprecate dance is over-engineering and the symbol can be removed directly. For a graduated / community-facing package (a-novel-kit/jwtand any future graduate — seewrite-go-kit's graduation rules), the workspace grep does not see external pkg.go.dev consumers, so staged removal still applies at zero in-org hits: the discipline is the public API contract, not the consumer count. Similarly, a single in-org consumer of agolibhelper may signal it belongs inlined into that consumer (write-go-kit's "should this go in golib?" bar) — agolib-specific rule, since graduated packages stay staged regardless.
Grep commands from the workspace root (the directory holding app/ and kit/), by symbol kind:
# A Go function / type / constant — name only, captures all import paths.
grep -rn "SymbolName" app/ kit/ --include='*.go'
# A proto message or RPC.
grep -rn "MessageName" app/ kit/ --include='*.proto' --include='*.go'
# A REST endpoint path or query parameter.
grep -rn 'apiPath' app/ kit/
# A renamed type — survey BOTH old and new names so consumer files using either show up.
grep -rn "HttpConfig\|HTTPConfig" app/ kit/ --include='*.go'
# A field on an exported struct (don't forget composite-literal usage).
grep -rn "\.FieldName\|FieldName:" app/ kit/ --include='*.go'Search both app/ (services) and kit/ (other shared libraries) — a graduated-package change rips
through jwt consumers as easily as service consumers. Capture the file list as a checklist in the
dependency PR's description, under a heading like "Consumer migration follow-ups", so it travels
with the PR through review.
Skipping this step has a specific failure mode: the dependency PR merges and releases, and weeks later someone finds a consumer nobody had on the radar — usually when it fails to compile after the removal step. Catch it now, not then.
The staged path itself
- Add the new path, non-breaking. New function / field / endpoint / column alongside the old,
which keeps working unchanged. Release this (a
feat:, not a breaking change — nothing broke yet). Mark the old path// Deprecated: use NewThingso consumers and pkg.go.dev see it. - Migrate every consumer to the new path. One PR per consumer (drawn from the step-0 list), each rebased on the Renovate bump that brings the release from step 1 (per the merge-order rule), each releasing once green. Tick consumers off the list as their PRs land; an empty list means step 3 is safe.
- Remove the old path. Once every consumer is off it, a follow-up PR in the dependency deletes
the old symbol/field/endpoint/column. This is the breaking change, now safe because nothing uses
the old path. Release it (a major bump for a graduated package /
pkg/go; for a service's internal-only contract, the version policy still applies).
The major-version bump in step 3 is signalling — it tells consumers "the old path is gone". It does not license skipping steps 1–2: a major bump that strands consumers is still a broken release.
This shape recurs everywhere; the layer skills carry the specifics:
- Go public APIs (
golib, graduated packages,pkg/go): add →// Deprecated:→ migrate → remove. Never change a function signature in place; add a new function. - Proto (
write-proto): never reuse a field number, never change a field's type, never remove a field that's still consumed — add new fields/messages/RPCs; mark old ones// Deprecated; remove only after every gRPC consumer is updated.buf breakingenforces a lot of this. - REST API (
write-openapi): add new endpoints/parameters/fields; keep old ones working; document the deprecation; remove only after clients have migrated. Optional request params and additive response fields are non-breaking; removing or repurposing either is breaking. - DB migrations (
write-sql): additive first (new nullable column, new table, new index) — and never modify a.up.sqlthat has merged tomaster; a "change" is a new migration. A destructive migration (drop column, narrow a type) ships only after the code that read the old shape is gone.
Use the BREAKING CHANGE: commit footer (git-conventions) only on step 3 — the actual removal —
not on step 1.
Quick checklist
When a change spans repos:
- Identify the dependency direction (who imports/consumes whom) and order the work bottom-up.
- On the consumer branch, pin to the dependency's commit SHA while its PR is a draft
(
go get module@<sha>, thengo mod tidy) — never areplace, never@branch. Before marking it ready, ask the developer to cut the dependency's release, then rebase on Renovate's bump to it. - Breaking a shared contract? Stage it (add → deprecate → migrate → remove); the current PR is the additive step only.
- For any breaking change to a public symbol,
grep -rnthe whole workspace (app/andkit/) for consumers before writing the dependency PR, and paste the file list into the PR description under "Consumer migration follow-ups". - Write the merge order in the PR description and/or
TaskCreate. - Merge the dependency PR → wait for its release tag → dispatch Renovate in the consumer and
land its bump, with only a migration pushed onto it when red → rebase the consumer branch on
master, without a pin of its own → confirm CI green → merge the consumer. Repeat up the chain. - Never merge a PR whose
go.mod(or Docker-image pin) points at a pseudo-version, a branch, orlatest.
When a cross-repo landing fails — the runbook
The saga's recovery mechanics (freeze, roll-forward, rollback, the release train) live in
coordinate-landing. This is the version-side decision tree: what a stuck or bad cross-repo
landing means for the pins and releases, and how to unwind it. A published release is immutable,
so cross-repo recovery is almost always roll forward — a new release that supersedes, rather
than rewritten history.
| Symptom | What happened | Do this |
| Consumer PR red on "released versions only" | Its go.mod still pins a pseudo-version (a dependency commit SHA), not a released tag | Wait for the dependency to release, land Renovate's bump to vX.Y.Z, rebase the consumer, push. Never merge on a pseudo-version. |
| Dependency released, consumer stuck in the Epic set | Publish-before-rollout half-done: dep shipped, consumer not bumped/merged | Dispatch Renovate in the consumer and rebase it on the bump; the merge-gate lands it with the set. The dep's release is immutable: roll forward, don't roll back. |
| An Epic partially landed | INV-1 violated — a member left the queue | The sweep freezes the survivors and rolls a landable stray forward within grace. If it can't land, fix it, or (last resort) epic-rollback the landed subset. See coordinate-landing. |
| A bad version shipped (released, then found broken) | A release is immutable — you can't un-publish | Roll forward: cut a patch that reverts the change, or epic-rollback the Epic (it lands compensating reverts and re-releases). Never delete a published tag a consumer may already pin. |
| A tag without its GitHub Release | The cut pushed the tag, then gh release create failed (GitHub answered HTTP 500) | Cut the next patch from the same master. No workflow publishes a Release for an existing tag, and production deploys an image only with an immutable Release from the release App, so the tag's images never deploy. The patch's generated notes cover both versions (genai v0.10.0, then v0.10.1). |
| The release train left a repo un-released | A dispatch/approval failed mid-train | Re-dispatch the train — it resumes from live tags and re-cuts only the unshipped. A parked run (release-env approval) is pending; approve it, then re-dispatch to record the receipt. |
| A contract-step removal broke a consumer | A consumer was missed in the expand→contract migration | Its build is red against the new dep release. Fast-follow: migrate the missed consumer and release it; until then it pins the previous dep release. |
epic-rollback is the one sanctioned "undo," and even it works by landing new compensating
commits and then re-releasing — never by rewriting a tag.
Common pitfalls
replacedirective for a local/branch dependency. Pin to a commit SHA instead, then rebase on Renovate's bump to the released tag before merging.- Merging a consumer on a pseudo-version pin. The pin must be a released
vX.Y.Zat merge time; the red "released versions only" check is telling you the truth. - Merging the consumer before the dependency is released. Dependency PR → release → Renovate bumps the consumer → the consumer rebases and merges, in that order.
- One PR that flips a public API / proto / endpoint / column old → new. Stage it, so there is always a both-work window.
BREAKING CHANGE:footer on the additive step. It goes on the removal step (step 3).- A major bump used as a shortcut. It signals "old path removed"; it does not excuse skipping the consumer migration.
- A release pinned in a feature branch. It conflicts with every Renovate merge on the same lines and asks the feature's reviewer for a dependency label. Dispatch Renovate and rebase on its bump instead, as bring a release through Renovate shows.
- Forgetting the chain. A consumer that is itself a dependency continues the dance one level up; write the whole order down.
- Skipping the step-0 consumer grep. A consumer found after step 3 shipped is a broken build, or worse a runtime hole, downstream.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

