MCPInstall

evolving-apis-and-schemas

Use when changing anything other systems or stored data already depend on — editing a .proto file, writing a database migration, adding or dropping a column, renaming a field, changing a response shape, changing an enum, or altering a queue message format. Triggers include "will this break clients", "is this backward compatible", "buf breaking", "ALTER TABLE", "migration", "deprecate this endpoint", and any change to a published API version.

Installs
90
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/osmontero/opencode-skills --skill evolving-apis-and-schemas
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads evolving-apis-and-schemas automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Evolving APIs and Schemas

Overview

A schema change is a distributed-systems problem wearing a one-line diff. The code that reads your data is not only the code you are about to deploy — it is also the version still running during the rollout, the client you do not control, and the rows written by last year's binary.

Core principle: Old readers and new readers must both be correct at every instant of the rollout. If there is a moment when that is false, the change needs more steps, not more confidence.

Companion skills: writing-plans for sequencing multi-release changes, verifying-before-completion before running anything destructive.

The Iron Law

ADDITIVE FIRST, DESTRUCTIVE LAST — NEVER IN THE SAME DEPLOY

Adding is reversible. Dropping, renaming, and narrowing are not. Between them must sit at least one fully deployed release where both shapes work, plus enough time to be sure you will not roll back across it.

No exceptions:

  • Not for "the old column is definitely unused" — prove it with a query, not a grep
  • Not for "we deploy everything together" — rollouts are not atomic, and rollbacks are not either
  • Not for "it's a tiny table"
  • Not for renames. A rename is a drop and an add, and it is the single most common way to take an outage

Step 1: Identify Your Consumers

Before classifying the change, list who reads this shape. The answer determines whether expand/contract is optional or mandatory.

ConsumerCan you redeploy it atomically with the producer?Consequence
Same binary, same structYesOrdinary refactor — compiler catches it
Another service you ownNo — rolling deploys mean both versions run at onceExpand/contract required
A client you do not control (SDK, mobile app, third party)No, everVersion it; the old shape is permanent
Persisted dataNo — you cannot redeploy a rowOld-format rows outlive every deploy
A queue or event logNo — in-flight messages were written by the old codeBoth formats must parse
A replica or a peer in a clusterNo — mixed versions during upgradeBoth directions must interoperate

The row most often forgotten is persisted data. Every value already on disk was written by code you no longer run. If the new code cannot read it, you have shipped a corruption bug that only appears on old records.

Step 2: Classify the Change

ClassExamplesRequires
SafeAdd an optional field; add a new endpoint, table, or RPC; add an index; widen a typeShip it
Expand/contractAdd a required field; rename; change a type; split or merge; drop anythingThe three-phase sequence below
Breaking — must versionChange the meaning of an existing name; remove from a published API; narrow accepted inputA new version, plus a deprecation window

The distinction between rows two and three: expand/contract works when you can eventually update every reader. When you cannot — a shipped mobile app, a public API — the old shape must keep working for as long as the support window says it will.

Step 3: The Expand/Contract Sequence

Three deploys, in order. Each is independently releasable and independently revertible.

1. EXPAND    Add the new shape. Nothing reads it yet.
             Writers write BOTH old and new.        ← revertible
             ─── deploy, verify, let it soak ───
2. MIGRATE   Backfill existing data into the new shape.
             Switch readers to the new shape.
             Writers still write both.              ← revertible
             ─── deploy, verify, let it soak ───
3. CONTRACT  Stop writing the old shape. Drop it.   ← the point of no return

Gates between phases, not just time: before contracting, confirm with a query that nothing writes the old shape and nothing reads it — application logs, pg_stat_statements, or an explicit counter you added in phase 1. "It has been two weeks" is not evidence.

Backfill is not a migration. Schema changes (DDL) go in the migration; moving data (DML) goes in a separate, resumable, batched job. A single UPDATE over ten million rows holds locks, blows out the WAL, and cannot be resumed after it times out.

Worked recipes for the common cases — add a NOT NULL column, rename, change a type, split a table, SQLite's rebuild procedure: references/migration-recipes.md.

Protobuf and gRPC

The field number is the wire identity. The name is not.

ChangeVerdict
Add a field with a new numberSafe
Add an RPC, message, or enum valueSafe for readers that tolerate unknowns
Delete a fieldSafe only with reserved on both number and name
Reuse a deleted field numberNever. Old data decodes into the wrong field, silently
Rename a fieldWire-safe; breaks JSON/text encoding and every generated client
Change a field's typeOnly within a compatible family; int32↔int64 yes, int32↔sint32 no
singular ↔ repeatedTreat as breaking — the semantics change even where the wire format survives
Add optional to an existing fieldWire-safe; gives you presence, changes nothing on the wire
Renumber an enum valueBreaking
Change a message or package nameBreaking — it is part of the service path and the Any type URL
Remove or rename an RPCBreaking
// Deleting a field: reserve BOTH, forever
message Document {
  reserved 4, 7 to 9;
  reserved "legacy_owner", "old_status";
}
buf breaking --against '.git#branch=main'    # wire this into review, not just CI

Enum zero values are the default for anything unset — make the zero value UNSPECIFIED, never a meaningful state, or you cannot distinguish "not set" from "set to the first option".

SQL Schema Changes

The danger is rarely correctness; it is lock duration. A statement that needs a brief exclusive lock will queue behind a long-running read, and everything else queues behind it.

SET lock_timeout = '3s';   -- fail fast instead of blocking the whole table

PostgreSQL:

OperationCostSafe form
ADD COLUMN nullable, or with a constant defaultBrief lock, no rewrite (PG 11+)Ship it
ADD COLUMN with a volatile defaultFull rewriteAdd nullable, backfill in batches, then set the default
SET NOT NULLFull scan under an exclusive lockADD CONSTRAINT ... CHECK (c IS NOT NULL) NOT VALID → VALIDATE CONSTRAINT → then SET NOT NULL is cheap
CREATE INDEXBlocks writes for the buildCREATE INDEX CONCURRENTLY — outside a transaction; check for an INVALID index if it fails
ADD FOREIGN KEYLocks both tables to validateNOT VALID, then VALIDATE CONSTRAINT
ALTER COLUMN TYPEFull rewrite, exclusiveExpand/contract via a new column
DROP COLUMNFast, irreversibleContract phase only

SQLite supports only ADD COLUMN, RENAME COLUMN, RENAME TABLE, and DROP COLUMN — and ADD COLUMN rejects NOT NULL without a default, PRIMARY KEY, and UNIQUE. Everything else requires the create-copy-drop-rename rebuild. See the recipes reference for the correct ordering, which matters: getting it wrong with foreign keys enabled silently drops rows.

HTTP and JSON APIs

  • Adding an optional response field is safe only if consumers ignore unknown fields. A client validating with additionalProperties: false breaks on additions — check before assuming.
  • Adding a required request field breaks every existing caller. Add it optional with a default.
  • Never repurpose a name. status meaning something new is worse than status_v2, because nothing fails loudly.
  • Error codes and pagination shapes are part of the contract even when they are not in the schema.
  • Version at the resource, not the whole API — a global /v2 forces a rewrite of endpoints that did not change.

Reversibility

Every migration answers: how do I undo this at 3am?

  • Additive migrations: the down is a drop. Write it.
  • Destructive migrations: there is no down. State that explicitly in the migration file, along with what recovery actually requires — restore from backup, replay from the event log, re-derive from another table.
  • Never write a down migration that silently loses data. A migration that "reverses" a drop by recreating an empty column is worse than one that refuses.
  • Test the down path against a copy of production data, not an empty dev database.

Verification

buf breaking --against '.git#branch=main'                       # proto
migrate up && migrate down 1 && migrate up                      # round-trip
psql -c "EXPLAIN (ANALYZE, BUFFERS) <the query the new index serves>"
  • Run the migration against a restored copy of production, timed. A migration that takes 40ms on 200 dev rows can take 40 minutes on the real table.
  • During phase 2, dual-read and compare: read both shapes, log any divergence, and let it run under real traffic before trusting the new one.
  • Confirm the old binary still passes its tests against the new schema. That is the rollback path, and it is the one nobody tests.

Red Flags

  • A migration file containing both ADD and DROP
  • ALTER TABLE ... RENAME in a codebase with more than one deployable
  • A single UPDATE with no WHERE and no batching
  • "The old field is unused" backed by a grep instead of a query
  • CREATE INDEX without CONCURRENTLY on a table anyone is writing to
  • A reused proto field number
  • A down migration you have never run
  • Deploying a schema change and the code that depends on it in the same release

All of these mean: split it into more deploys.

Common Mistakes

Renaming in place. The compiler will tell you about your own code and nothing about the other binary that is still running.

Treating the deploy as atomic. During a rolling deploy both versions serve traffic — often for many minutes, and indefinitely if the rollout stalls.

Forgetting old rows. New code must parse every format ever written, until you have provably rewritten them all.

Backfilling inside the schema migration. It holds locks for the duration and cannot be resumed.

Trusting the ORM's auto-migration. It generates the destructive form because it does not know about your other readers.

Skipping the soak. Phase 3 is irreversible; the only thing protecting you is having run phase 2 long enough to be sure.

FAQ

What does the evolving-apis-and-schemas skill do?

Use when changing anything other systems or stored data already depend on — editing a .proto file, writing a database migration, adding or dropping a column, renaming a field, changing a response shape, changing an enum, or altering a queue message format. Triggers include "will this break clients", "is this backward compatible", "buf breaking", "ALTER TABLE", "migration", "deprecate this endpoint", and any change to a published API version.

How do I install evolving-apis-and-schemas?

Run npx skills add https://github.com/osmontero/opencode-skills --skill evolving-apis-and-schemas in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/evolving-apis-and-schemas/ (personal) or .claude/skills/evolving-apis-and-schemas/ (project).

Which AI agents can use evolving-apis-and-schemas?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is evolving-apis-and-schemas safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains evolving-apis-and-schemas?

It is published by osmontero in osmontero/opencode-skills. It has 90 recorded installs.

More from osmontero/opencode-skills

View all →

designing-frontend-interfaces

osmontero/opencode-skills

Use when building or restyling any web interface — landing pages, dashboards, marketing sites, web apps, React/Vue components, HTML/CSS layouts, artifacts, posters — or when asked to make a UI look better, more polished, more distinctive, less generic, or less "AI-generated". Also use when a design brief, art direction, visual identity, type scale, or color system is needed before implementation.

Frontend & Design650 installs

designing-user-experience

osmontero/opencode-skills

Use when designing how an interface behaves rather than how it looks — user flows, navigation and information architecture, forms and validation, loading/empty/error states, confirmation and undo for destructive actions, button and error message wording, feedback for slow operations, or mobile and touch behavior. Also use when a UI looks finished but breaks on bad input, slow networks, no data, or first use.

Frontend & Design462 installs

reviewing-interface-quality

osmontero/opencode-skills

Use when asked to review, critique, audit, or improve an existing user interface — "review this UI", "why does this look generic", "make this more polished", "design feedback", "does this look AI-generated", "audit this page". Also use as the final gate before shipping any frontend work, and when a UI renders correctly but feels unfinished, cheap, or inconsistent without an obvious cause.

Frontend & Design365 installs

building-accessible-interfaces

osmontero/opencode-skills

Use when building or auditing any web UI for accessibility — keyboard navigation, screen readers, focus management, ARIA, color contrast, form labels and error announcements, reduced motion, or touch target size. Triggers include "is this accessible", "WCAG", "a11y", "screen reader", "keyboard navigation", "focus trap", "contrast ratio", "aria-label", or building modals, menus, tabs, comboboxes, toasts, and custom controls that must work without a mouse.

Frontend & Design350 installs

systematic-debugging

osmontero/opencode-skills

Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes

verifying-before-completion

osmontero/opencode-skills

Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always

157 installs

Popular code quality & workflow skills

View category →

grill-me

mattpocock/skills

A relentless interview to sharpen a plan or design.

Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

handoff

mattpocock/skills

Compact the current conversation into a handoff document for another agent to pick up.

domain-modeling

mattpocock/skills

Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.

codebase-design

mattpocock/skills

Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.

diagnosing-bugs

mattpocock/skills

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.