Skip to main content

tool-migration-cutover

Migrating between tools (Dependabot to Renovate, PAT to GitHub App, one linter to another). Use when removing the incumbent, to verify the replacement actually runs rather than merely configured.

Datos de origen

Repositorio
laurigates/claude-plugins
Última actividad en el origen
12 de septiembre de 2026 a las 18:54
Idioma detectado de SKILL.md
inglés
Estrellas
58
Forks
6

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
tool-migration-cutover
description
Migrating between tools (Dependabot to Renovate, PAT to GitHub App, one linter to another). Use when removing the incumbent, to verify the replacement actually runs rather than merely configured.
allowed-tools
Bash, Read, Grep, Glob, Edit, TodoWrite
created
2026-08-08T00:00:00.000Z
modified
2026-09-12T00:00:00.000Z
reviewed
2026-09-12T00:00:00.000Z
# Tool-Migration Cutover: Verify the Replacement Is *Operational* Before Deprecating the Incumbent When migrating from one tool to another (Dependabot → Renovate, a PAT-based workflow → a GitHub App, one linter/CI/runner → another), **do not remove the incumbent until the replacement is observed actually doing the job** — not merely *configured* to. "Config exists" and "config works" are different claims, and the gap between them is where coverage silently drops to zero. ## The failure mode The incumbent is removed on the strength of the replacement's *presence*: 1. The replacement's config is committed and looks correct. 2. Someone reasons "Renovate is set up, so Dependabot is redundant — remove it." 3. The replacement was never actually running (broken credentials, missing step, unprovisioned secret, quota), so **neither** tool is now doing the work. 4. The gap is invisible: no error fires for "nothing is updating dependencies." Config presence is not evidence of operation. A scheduled runner can fail every single run and leave no trace in the place you're looking (the repo's PR list stays empty, which reads identically to "no updates needed"). ## The rule **Removal of the incumbent is gated on a positive operational signal from the replacement**, observed on the *actual* target repos: | Replacement | Positive signal to require before deprecating incumbent | |---|---| | Renovate | A successful runner execution **and** a Dependency Dashboard issue / `renovate/*` branch / PR on the target repo | | A CI/lint tool | A green run of the new check on a real PR, not just the workflow file merged | | A GitHub App replacing a PAT | A workflow run that successfully mints **and uses** the App token | | A new deploy path | One real deploy through the new path that reaches the target | Until that signal exists, **stage the deprecation as a draft** (draft PRs, an un-merged branch, a feature-flag off) so the work is ready the instant the signal lands — but cannot be merged prematurely by you or anyone else. ## How to check operation (don't trust config presence) - **Runner actually ran and succeeded** — `gh run list --workflow=<f> -L 5` (all `failure` = it has never worked). Read the failed log; an ~8s failure at step 1 is usually a credential/token problem, a ~2s zero-step failure is usually quota. - **Side effects appeared on the target** — the dashboard issue, the branch, the PR. `gh issue list --search "Dependency Dashboard in:title"`, `gh api repos/<o>/<r>/branches --jq '.[].name|select(startswith("renovate/"))'`. - **Credentials/secrets are present where consumed** — the secret/variable on the consuming repo, not just "set upstream" (in the IaC runner/CI/a vault). The push from upstream to the repo is a separate step that can itself be blocked. ## Worked example — Dependabot → Renovate (Bun), 2026-06 The premise "Renovate already manages these repos, overlapping with Dependabot" was false: the centralized autodiscover runner **failed every run** (unprovisioned GitHub App → empty `app-id` → token mint failed), so Dependabot was the *only* working dependency automation. Deprecating it then would have left 9 repos with no updates. Correct sequence: fix the latent runner bug, **stage the 9 `dependabot.yml` deletions as draft PRs**, hand off the (manual, user-only) App provisioning, and gate the draft merges on a verified Renovate run. Two Renovate/Bun facts that fell out of the same investigation, worth not re-deriving: - **There is no bun `postUpdateOptions` value** (`bunDedupe` does not exist; allowed values are npm/pnpm/yarn/bundler/go/nuget only). An invented value fails Renovate's `allowedValues` validation and breaks the **whole config** — for the *global* self-hosted config, that breaks every repo. Verify enum values against the Renovate docs before adding them. - Renovate updates and commits `bun.lock` **natively** when it patches `package.json` (it runs the package manager and commits both). No option is needed for "generate a matching lockfile"; `lockFileMaintenance` is the separate periodic full-refresh. ## When it bites - Dependency-bot swaps (Dependabot ↔ Renovate), where "no PRs" looks the same whether the tool is off or just has nothing to do. - Credential/secret migrations where the value is set in the orchestrator (an IaC runner, a vault, org secrets) but the *push to the consuming repo* hasn't run. - CI tool replacements merged as a workflow file but never exercised on a PR. ## The mirror failure: the incumbent removed too *late* Everything above guards against cutting over too early. The opposite failure is just as common and noisier: the migration switches the **canonical** path to the new tool but never unwires the old one, and the leftover does not sit inert — it **fights** the replacement on every commit. The shape (observed 2026-07, Comfy-Org/registry-web #272): 1. A migration commit updates the `fmt`/`fix` scripts and CI to the new tool. 2. `lint-staged` still runs the old formatter on every commit. 3. The old config is the *opposite* style of the new one, so the pre-commit hook **reformats correct code to the wrong style** on every commit. The tell is a 3-line edit that produces a 400-line diff. It reads as "the formatter ran", not as "two formatters disagree and the loser is mangling my files" — which is why it survives for months. ### Establish which tool is canonical — mechanically, don't reason about it When two formatters or linters are both present, the committed code matches exactly one. Ask them: ``` oxfmt --check <file> # exit 0 -> the code matches oxfmt prettier --check <file> # warns -> the code does NOT match prettier ``` The one CI runs is canonical; the one whose `--check` **fails on already-committed code** is the leftover. Run it — do not argue about which *should* be right. ### Swapping the command is not enough — simulate the real hook Replacing `prettier --write` with `oxfmt` in `lint-staged` looks done and can be silently broken: oxfmt is a JS/TS formatter and **errors on a `package.json` target**, where prettier silently handled json/css/md. Run the actual hook against staged files before trusting it: ``` git add <a badly-formatted .tsx> && bunx lint-staged ``` It must succeed *and* fix in the canonical style. ### Sweep every wiring, not just the loud one Grep the whole repo surface for the old tool before calling the migration done. In the case above the leftovers were `lint-staged`, `.prettierrc`, `.eslintrc.json`, a dead path-scoped workflow whose target directory no longer existed, a `_lint` script, and **eight** unused devDependencies. Verify removal does not break a real consumer — inert `// eslint-disable` comments in generated code are directives, not imports, and do not require the package. ## Rationale Removing a working tool is cheap to do and expensive to discover undone — the loss is a *non-event* (updates that silently stop happening), so nothing alarms. The mirror is worse in one respect: an incomplete migration advertises the new tool while the old one quietly corrupts work, and the cost is paid on *every* commit by whoever touches a file, usually misattributed to "the formatter did something weird". Gating on a positive operational signal, and staging the removal as a draft in the meantime, costs one extra "is it actually running?" check and converts a silent multi-week coverage gap into a no-op wait. This is the migration-time sibling of `verify-upstream-before-patching.md` (check reality before acting) and `ci-cd-multirepo.md` (fetch-first before diagnosing CI).
Ver en GitHub