Skip to content
Back to skills

Deprecation And Migration

ASecurity

Guides deprecating and removing old systems, APIs, or code, and migrating consumers to a replacement. Use when replacing a library, API, or internal system with a new one, sunsetting a feature, deciding whether to maintain or remove legacy code, or planning how a new system will eventually be retired.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 16, 2026
developmentpythonrustgogitapisecuritydocumentation

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 16, 2026

npx -y skills add shennawardana23/skillme --skill deprecation-and-migration --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Deprecation And Migration?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Deprecation And Migration
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shennawardana23-deprecation-and-migration/badge)](https://www.skillsdirectory.com/skills/shennawardana23-deprecation-and-migration)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: deprecation-and-migration
description: Guides deprecating and removing old systems, APIs, or code, and migrating consumers to a replacement. Use when replacing a library, API, or internal system with a new one, sunsetting a feature, deciding whether to maintain or remove legacy code, or planning how a new system will eventually be retired.
license: Apache-2.0
metadata:
  version: "0.1.0"
---

# Deprecation and Migration

Code is a liability, not an asset: every line has an ongoing cost in
tests, dependency updates, security patches, and onboarding overhead.
Deprecation is the discipline of removing code that no longer earns that
cost back. Most organizations are good at building; few are good at
removing — this skill is about the removing half.

## The deprecation decision

Answer these in order before deprecating anything:

1. **Does it still provide unique value?** If yes, maintain it; stop here.
2. **How many consumers depend on it?** Quantify with actual usage data
   (call metrics, import graph, dependency scan) — not a guess.
3. **Does a working replacement exist?** If not, build and prove the
   replacement in production first. Never deprecate without an
   alternative already available.
4. **What's the per-consumer migration cost?** Trivially automatable
   (codemod, `go fix`-style rewrite) vs. manual and high-effort changes
   the decision.
5. **What's the cost of *not* deprecating?** Security exposure, engineer
   time maintaining it, complexity tax on everything nearby.

## Compulsory vs. advisory

| Type | When | Mechanism |
|---|---|---|
| **Advisory** (default) | Old system is stable; migration optional | `// Deprecated:` comments, linter warnings, docs. Consumers migrate on their own schedule. |
| **Compulsory** | Security issue, unsustainable maintenance cost, or the old system blocks other work | Hard removal date, migration tooling provided, active tracking of remaining consumers |

Compulsory deprecation without migration tooling is just an announcement
that ships the work onto consumers — that's the "Churn Rule": whoever
owns the thing being deprecated owns migrating its users, or ships a
backward-compatible replacement that needs no migration at all.

## Go-specific mechanics

Mark deprecated exported identifiers with the standard doc-comment
convention `go vet` and IDEs recognize — this makes the deprecation
visible at every call site, not just in documentation nobody reads:

```go
// LegacyCharge processes a payment through the old gateway.
//
// Deprecated: use Charge instead; LegacyCharge will be removed after
// all callers migrate (tracked in JIRA-1234).
func LegacyCharge(amount int64) error { ... }
```

For a package whose exported API must change incompatibly, Go modules
require a new major version to live at a new import path
(`module example.com/pkg/v2`) rather than in place — this is the
mechanism that lets old and new major versions coexist in the module
graph without one silently breaking the other; see `git-workflow-and-versioning`
for how the version number itself should reflect the break.

## Migration patterns

**Strangler pattern** — shift traffic from old to new incrementally,
removing the old system only once it's fully idle:

```
Phase 1: new handles 0%,  old handles 100%
Phase 2: new handles 10%  (canary)
Phase 3: new handles 50%
Phase 4: new handles 100%, old idle
Phase 5: remove old
```

**Adapter pattern** — wrap the new implementation behind the old
interface so consumers don't need to change at all during the migration
window:

```go
// Old interface, delegating to the new implementation underneath.
type LegacyTaskService struct{ new *NewTaskService }

func (l *LegacyTaskService) GetTask(id int) (OldTask, error) {
    t, err := l.new.FindByID(context.Background(), strconv.Itoa(id))
    if err != nil {
        return OldTask{}, err
    }
    return toOldFormat(t), nil
}
```

**Feature-flag migration** — switch individual consumers over one at a
time, with an instant revert path if the new path misbehaves:

```go
func getTaskService(flags FeatureFlags, userID string) TaskService {
    if flags.Enabled("new-task-service", userID) {
        return newTaskService
    }
    return legacyTaskService
}
```

## The migration process

1. **Build the replacement** — proven in production, documented, covers
   every critical use case of the old system (not just the common path).
2. **Announce with a concrete plan** — status, replacement, migration
   guide with copy-pasteable steps, and either an advisory note or a hard
   removal date.
3. **Migrate incrementally, one consumer at a time** — identify
   touchpoints, switch, verify behavior matches, remove the old
   reference, confirm no regression. Track remaining consumers explicitly
   (a tracking issue, a dashboard of remaining call sites) rather than
   trusting memory.
4. **Remove only after verified zero usage** — check metrics/logs/import
   graph, not just "I think everyone migrated." Remove the code, its
   tests, its docs, its config, and the deprecation notice itself in the
   same change — a deprecation notice for code that no longer exists is
   its own kind of clutter.

## Zombie code

Code with active consumers but no owner, no recent commits, and no
one accountable for its failing tests or unpatched CVEs. It cannot stay
in limbo: either assign an owner and maintain it for real, or deprecate
it with the concrete plan above. Signs: 6+ months with no commits despite
active consumers; documentation referencing systems that no longer exist;
known vulnerabilities nobody has scheduled a fix for.

## Gotchas

- Hyrum's Law applies to removal, not just to APIs: with enough
  consumers, undocumented timing quirks and even bugs in the old system
  become depended-on behavior — "the replacement is functionally
  equivalent" is not sufficient; verify it against real consumer
  behavior, not just the documented contract.
- A `// Deprecated:` comment with no tracking issue and no removal
  target quietly becomes permanent — advisory deprecations that have sat
  for years with zero migration progress are a signal to either invest in
  migration tooling or accept the code is not actually going away.
- Deleting the old code before confirming zero callers via actual usage
  data (not team memory) is the single most common cause of migration
  incidents — a caller nobody remembered still existed breaks silently.
- Running two systems that do the same thing "just for a while" rarely
  stays temporary without an explicit forcing function (a removal date,
  a tracked issue) — indefinite parallel maintenance is strictly more
  expensive than either finishing the migration or reverting it.

## Real-world grounding

Python's 2-to-3 migration (Python 3 released 2008, Python 2 end-of-life
January 2020) is the widely cited cautionary case for underestimating
migration cost: the language broke backward compatibility without a
sufficiently long overlapping-support and tooling runway relative to the
size of the installed ecosystem, and the transition took over a decade
longer than planned industry-wide — the lesson generalizes directly:
compulsory deprecation without adequate migration tooling and a realistic
timeline for your actual consumer base, not an optimistic one, is what
turns a deprecation into a multi-year drag on the whole ecosystem around
it.

## Verification

- [ ] A working, production-proven replacement exists before deprecation is announced
- [ ] The deprecation type (advisory vs. compulsory) matches the actual urgency, not just convenience
- [ ] Go: deprecated exported identifiers carry a `// Deprecated:` comment; incompatible API changes get a new major-version import path
- [ ] Migration is tracked per-consumer, not assumed complete from memory
- [ ] Zero-usage is confirmed via metrics/logs/import graph before deletion
- [ ] Old code, its tests, its docs, its config, and its deprecation notice are all removed in the same change

Files in this skill

  • SKILL.md7.8 KB
  • evals/evals.json3.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.