GitHub Models is fully retired on July 30, 2026, with brownouts on July 16 and 23 that will break your calls early. The decision of where to move is its own piece; this one is the mechanical part — the exact edit — and it's smaller than the deadline makes it feel.
Here's why: GitHub Models implemented the OpenAI Chat Completions API. So does every replacement. Your actual create() calls don't change at all. You change where the client points and how it authenticates. That's the entire migration.
1. What you're replacing#
The GitHub Models call looks like this — an OpenAI SDK client aimed at the GitHub endpoint, authenticated with a token that carries the models:read permission:
from openai import OpenAI
import os
client = OpenAI(
base_url="https://models.github.ai/inference",
api_key=os.environ["GITHUB_TOKEN"], # a PAT with models:read
)
resp = client.chat.completions.create(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "ping"}],
)
Everything below the client = ... line survives untouched. You are only editing the two arguments to OpenAI(...) and the model string.
2. The swaps, one edit each#
Each destination is the same three-part change: base URL, key, model ID.
| Destination | base_url | api_key | Model ID |
|---|---|---|---|
| OpenRouter | https://openrouter.ai/api/v1 | OpenRouter key | openai/gpt-4o-mini |
| OpenAI (direct) | (omit — SDK default) | OpenAI key | gpt-4o-mini |
| Anthropic | https://api.anthropic.com/v1/ | Anthropic key | a claude model ID |
| Moonshot Kimi | https://api.moonshot.ai/v1 | Moonshot key | kimi-k2.7-code |
| Local Ollama | http://localhost:11434/v1 | "ollama" (ignored) | llama3.3 |
OpenRouter is the smallest diff because it keeps the namespaced model IDs (openai/gpt-4o-mini) and the many-models-one-URL shape you're losing:
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
# model="openai/gpt-4o-mini" — unchanged
Going local with Ollama keeps dev and CI at zero per-token cost; the api_key is required by the SDK but ignored by the server:
client = OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama",
)
# model="llama3.3" (after: ollama pull llama3.3)
3. Do it once: put the endpoint behind one env var#
The migration is easy because the format is shared. Make the next one free by never hard-coding the endpoint again — read it from the environment and route every call through one client.
Instead of editing code per provider, read all three moving parts from the environment and build a single shared client:
# llm.py — import this everywhere, construct the client nowhere else
from openai import OpenAI
import os
client = OpenAI(
base_url=os.environ.get("LLM_BASE_URL") or None, # None → OpenAI default
api_key=os.environ["LLM_API_KEY"],
)
MODEL = os.environ["LLM_MODEL"]
from llm import client, MODEL
resp = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "ping"}],
)
Now switching providers is editing three environment variables — no code in the diff, no logic redeployed. This is the same discipline behind a provider-agnostic agent: make the model vendor a variable, not a constant. Pair it with real error handling and fallbacks and one dead provider stops being an outage.
4. Check CI — GitHub Models is hiding in your workflows#
The trap: GitHub Actions injects GITHUB_TOKEN automatically, so a workflow step can call the inference API with no explicit key in the YAML. That usage is invisible until July 30 kills it. Before the brownouts, grep the whole repo — including .github/workflows — for the two fingerprints:
grep -rn "models.github.ai\|models:read" . --include="*.py" --include="*.js" --include="*.yml" --include="*.yaml"
For each hit: add the new provider's key as an Actions secret, set LLM_BASE_URL, LLM_API_KEY, and LLM_MODEL in the job's env:, and the wrapper from step 3 runs unchanged in CI. Test it against the endpoint now — the July 16 brownout is a much better place to discover a broken workflow than the hard cutoff on the 30th.
That's the whole job: two lines to point somewhere new, three env vars to never do it again, one grep so CI doesn't ambush you.



