Build a deprecation calendar for models and APIs you depend on
For developers who own production integrations and need advance warning before a model, endpoint, or SDK version disappears. This walkthrough gives you a working deprecation calendar fed from a repo-owned inventory, ICS output, and CI checks that fail when a sunset date is too close or missing.
TL;DR — Put your external model/API dependencies in a versioned inventory file, generate an iCalendar feed from it, and fail CI when any dependency has no deprecation date or is inside your warning window. The single biggest win is treating deprecation dates like certificate expirations: machine-readable, reviewed in Git, and checked on every merge. Reading time: ~5 min
Goal
When you finish, your repo will contain a machine-readable deprecation inventory, a script that turns it into a calendar file and CI report, and a scheduled CI job that warns or fails before a model or API you depend on reaches its deprecation or sunset date.
Prerequisites
- Git access to the application repo you want to protect
- CI access for that repo (GitHub Actions, GitLab CI, Jenkins, or equivalent)
- Python 3.11+ — check with:
python3 --version
pipavailable for your Python install — check with:
python3 -m pip --version
- A calendar client that can subscribe to an
.icsfile, or a place to publish one internally - The list of external dependencies you actually call in production: model IDs, API base URLs, SDK names, and current versions
- The vendor documentation URLs where deprecation/sunset notices are published
Steps
Step 1: Create a repo-owned dependency inventory
Create a file at ops/deprecations/dependencies.yaml:
services:
- name: openai-chat-primary
type: model
vendor: openai
identifier: gpt-4.1
environment: production
owner: team-platform
docs_url: https://vendor.example/models
announced_deprecation_date: 2026-11-01
sunset_date: 2027-02-01
replacement: gpt-4.2
warning_days: 120
- name: embeddings-service
type: api
vendor: internal-proxy-to-vendor
identifier: https://api.example.com/v1/embeddings
environment: production
owner: team-search
docs_url: https://vendor.example/changelog
announced_deprecation_date: 2026-08-15
sunset_date: 2026-12-15
replacement: https://api.example.com/v2/embeddings
warning_days: 90
- name: payments-sdk
type: sdk
vendor: stripe
identifier: stripe-python==11.6.0
environment: production
owner: team-billing
docs_url: https://docs.stripe.com/changelog
announced_deprecation_date: 2026-09-01
sunset_date: 2027-03-01
replacement: stripe-python==12.x
warning_days: 120
What you should see when this step succeeds: the file exists in Git and every production dependency has one entry with owner, docs_url, and sunset_date filled in.
Step 2: Add a generator/check script
Create ops/deprecations/check_deprecations.py:
#!/usr/bin/env python3
from __future__ import annotations
import sys
from datetime import date, datetime, timedelta
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
INPUT = ROOT / "dependencies.yaml"
OUT = ROOT / "deprecations.ics"
def parse_date(value: str) -> date:
return datetime.strptime(str(value), "%Y-%m-%d").date()
def ics_escape(s: str) -> str:
return str(s).replace("\\", "\\\\").replace(";", "\\;").replace(",", "\\,").replace("\n", "\\n")
def main() -> int:
data = yaml.safe_load(INPUT.read_text())
today = date.today()
errors = []
warnings = []
lines = ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//deprecation-calendar//EN"]
for item in data.get("services", []):
name = item["name"]
owner = item.get("owner", "unknown")
docs_url = item.get("docs_url", "")
warning_days = int(item.get("warning_days", 90))
announced = item.get("announced_deprecation_date")
sunset = item.get("sunset_date")
if not announced or not sunset:
errors.append(f"ERROR: {name}: missing announced_deprecation_date or sunset_date")
continue
announced_d = parse_date(announced)
sunset_d = parse_date(sunset)
warn_d = sunset_d - timedelta(days=warning_days)
if warn_d <= today:
warnings.append(f"WARN: {name}: sunset {sunset_d.isoformat()} is within {warning_days} days; owner={owner}")
if sunset_d <= today:
errors.append(f"ERROR: {name}: sunset {sunset_d.isoformat()} has passed; owner={owner}")
desc = f"{item['type']} {item['identifier']}\\nOwner: {owner}\\nReplacement: {item.get('replacement', 'n/a')}\\nDocs: {docs_url}"
for kind, dt, summary in [
("announce", announced_d, f"Deprecation announced: {name}"),
("warning", warn_d, f"Warning window starts: {name}"),
("sunset", sunset_d, f"Sunset date: {name}"),
]:
lines.extend([
"BEGIN:VEVENT",
f"UID:{name}-{kind}@deprecation-calendar",
f"DTSTAMP:{today.strftime('%Y%m%d')}T000000Z",
f"DTSTART;VALUE=DATE:{dt.strftime('%Y%m%d')}",
f"SUMMARY:{ics_escape(summary)}",
f"DESCRIPTION:{ics_escape(desc)}",
"END:VEVENT",
])
lines.append("END:VCALENDAR")
OUT.write_text("\r\n".join(lines) + "\r\n")
for msg in warnings:
print(msg)
for msg in errors:
print(msg, file=sys.stderr)
if errors:
return 2
if warnings:
return 1
print("OK: no upcoming sunsets inside warning windows")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Install the dependency and run it:
python3 -m pip install pyyaml
python3 ops/deprecations/check_deprecations.py; echo $?
What you should see when this step succeeds: ops/deprecations/deprecations.ics is created and the script exits 0, 1, or 2 with readable lines like WARN: or ERROR:.
Step 3: Interpret exit codes and fix inventory gaps
Run the checker directly after adding your real dependencies:
python3 ops/deprecations/check_deprecations.py
Typical output when dates are missing:
ERROR: openai-chat-primary: missing announced_deprecation_date or sunset_date
ERROR: embeddings-service: sunset 2026-07-01 has passed; owner=team-search
Typical output when a sunset is approaching:
WARN: payments-sdk: sunset 2027-03-01 is within 120 days; owner=team-billing
What you should see when this step succeeds: every production dependency either has a complete date set or the script is intentionally failing, giving you a concrete list to fix.
Step 4: Add a CI job that runs on every push and on a schedule
If you use GitHub Actions, create .github/workflows/deprecations.yml:
name: deprecations
on:
push:
branches: [ main ]
pull_request:
schedule:
- cron: "17 7 * * 1"
jobs:
check-deprecations:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python -m pip install pyyaml
- run: python ops/deprecations/check_deprecations.py
- uses: actions/upload-artifact@v4
if: always()
with:
name: deprecations-ics
path: ops/deprecations/deprecations.ics
What you should see when this step succeeds: the CI job appears in your pipeline and uploads deprecations.ics even when the check warns or fails.
Step 5: Publish the calendar file somewhere your team can subscribe to
Commit the files and push:
git checkout -b chore/deprecation-calendar
git add ops/deprecations .github/workflows/deprecations.yml
git commit -m "Add deprecation inventory, ICS generator, and CI check"
git push -u origin chore/deprecation-calendar
If your repo is already published through internal docs or static hosting, copy ops/deprecations/deprecations.ics into that published path. If not, attach the artifact URL from CI to your team docs page.
What you should see when this step succeeds: you have a stable URL or downloadable artifact for deprecations.ics that your team can subscribe to.
Step 6: Add a code-review gate for new external dependencies
Create docs/dependency-intake.md:
## New external model/API checklist
- Add the dependency to `ops/deprecations/dependencies.yaml`
- Set `owner` to the team slug
- Add `docs_url` for vendor deprecation notices
- Add `announced_deprecation_date` and `sunset_date`; if unknown, block merge until vendor policy is found
- Set `replacement`
- Set `warning_days` to 90, 120, or 180 based on migration complexity
What you should see when this step succeeds: PR reviewers have a literal checklist, and new dependencies stop bypassing the calendar.
Verify it works
Run the checker locally:
python3 ops/deprecations/check_deprecations.py; echo $?
ls -l ops/deprecations/deprecations.ics
Expected success output shape:
OK: no upcoming sunsets inside warning windows
0
-rw-r--r-- 1 you staff 1842 Mar 1 10:22 ops/deprecations/deprecations.ics
Inspect the calendar file header:
head -20 ops/deprecations/deprecations.ics
Expected output shape:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//deprecation-calendar//EN
BEGIN:VEVENT
UID:openai-chat-primary-announce@deprecation-calendar
DTSTAMP:20260301T000000Z
DTSTART;VALUE=DATE:20261101
SUMMARY:Deprecation announced: openai-chat-primary
Verify CI behavior:
- A dependency with missing dates should fail the job with exit code
2. - A dependency inside the warning window should mark the step failed in strict CI, or at minimum print
WARN:lines in logs. - The uploaded artifact should contain
deprecations.ics.
Common pitfalls
Missing dates for dependencies already in production
Mistake: adding owner and docs_url but leaving announced_deprecation_date or sunset_date blank.
Symptom: CI log shows ERROR: <name>: missing announced_deprecation_date or sunset_date and exits 2.
Fix: populate both dates from the vendor changelog or policy page before merge.
Tracking only SDK versions and not the underlying model or endpoint
Mistake: inventory includes client-lib==x.y.z but not the actual hosted model ID or API path you call.
Symptom: your calendar stays green while the hosted model or endpoint is retired.
Fix: add separate entries for SDK, model ID, and endpoint URL when they can deprecate independently.
Warning windows that are shorter than your migration lead time
Mistake: setting warning_days: 30 for a dependency that needs contract review, load testing, or prompt regression work.
Symptom: the first warning arrives after sprint planning, and the migration becomes an emergency.
Fix: set warning_days to 120 or 180 for high-risk dependencies.
Publishing an ICS file once and never regenerating it
Mistake: uploading deprecations.ics manually to docs or storage and forgetting to refresh it after inventory changes.
Symptom: subscribed calendars show stale events even though dependencies.yaml changed.
Fix: publish the generated file from CI on every merge to main.
Treating vendor docs URLs as optional
Mistake: omitting docs_url because the date is currently known.
Symptom: six months later nobody knows where the date came from or whether it changed.
Fix: add the exact changelog or lifecycle-policy URL for every entry so the next reviewer can re-verify it fast.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI