Adding Groq Model Deprecation Alerts & Migrating to gpt‑oss‑120b in the Content‑Automation Pipeline
TL;DR: I added an email‑based alert that fires when a Groq model is deprecated, listed the still‑available models, and updated the generator to use the new gpt‑oss‑120b endpoint. The change fixes the “SyntaxError: Unexpected token ‘<’” that broke our content‑automation run and gives the team visibility on model lifecycle.
The Problem
Our nightly content‑automation job started failing with:
SyntaxError: Unexpected token '<' in src/content_generator.py line 142
The stack trace pointed to the _list_available_models helper that parses the Groq model list from the API response. Groq announced the deprecation of the gpt‑oss‑20b model, but our code still tried to request it, receiving an HTML error page (<html>…) instead of JSON. The JSON parser threw the < token error, halting the entire pipeline and preventing the generation of Medium, Substack, and Dev.to posts for the day.
Beyond the immediate crash, we had no visibility when a model was retired, forcing us to discover the problem only after the CI job failed.
What I Tried First
My first instinct was to catch the JSON parsing exception and fallback to the previous model:
def _list_available_models(self) -> list[str]:
try:
resp = httpx.get(self.MODEL_ENDPOINT)
data = resp.json()
return [m["id"] for m in data["models"]]
except json.JSONDecodeError:
# fallback to hard‑coded list
return ["gpt-oss-20b"]
This kept the pipeline alive, but it silently kept using a deprecated model, causing the same HTML response on every run. The alert never fired, and we kept generating content with a model that no longer existed, leading to degraded output quality.
I also attempted to patch the endpoint URL in the config file, but the config is shared across multiple environments, and a hard‑coded change would break other branches still using the older model.
The Implementation
1. Detecting Deprecation and Sending Alerts
The core of the solution lives in src/content_generator.py. I expanded _list_available_models to return both the list of models and a flag indicating whether any of the previously used models are missing. Then I introduced _send_deprecation_alert that composes an email with the list of still‑available models and a link to the repository’s GitHub Secrets page (so the team can update the secret that stores the model name).
# src/content_generator.py
import smtplib
from email.message import EmailMessage
import os
import httpx
MODEL_ENDPOINT = "https://api.groq.com/v1/models"
DEPRECATED_MODELS = {"gpt-oss-20b"}
def _list_available_models(self) -> tuple[list[str], bool]:
"""Return (available_models, has_deprecated)"""
try:
resp = httpx.get(MODEL_ENDPOINT, timeout=5)
resp.raise_for_status()
data = resp.json()
models = [m["id"] for m in data["models"]]
has_deprecated = any(m in DEPRECATED_MODELS for m in models) is False
return models, has_deprecated
except Exception as e:
# Propagate a clear error; the caller will log it.
raise RuntimeError(f"Failed to fetch Groq models: {e}")
def _send_deprecation_alert(self, missing: list[str]) -> None:
"""Email the team about deprecated models."""
secret_link = "https://github.com/yourorg/content-automation/settings/secrets"
body = (
f"The following Groq models are no longer available: {', '.join(missing)}\n"
f"Available models: {', '.join(self.available_models)}\n"
f"Update the `GROQ_MODEL` secret here: {secret_link}"
)
msg = EmailMessage()
msg["Subject"] = "[Alert] Groq model deprecation detected"
msg["From"] = os.getenv("ALERT_SENDER")
msg["To"] = os.getenv("ALERT_RECIPIENTS")
msg.set_content(body)
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
smtp.login(os.getenv("ALERT_SENDER"), os.getenv("ALERT_PASSWORD"))
smtp.send_message(msg)
Key changes:
- The function now returns a tuple, allowing the caller to decide whether to proceed or abort.
- Deprecation detection is a simple set subtraction: if any model we expect (
DEPRECATED_MODELS) is missing, we treat it as deprecated. - The alert includes a direct link to the GitHub Secrets page, making it trivial for anyone to rotate the secret.
2. Migrating to gpt‑oss‑120b
Once we know the older model is gone, we need to switch to the newer, larger model. I added a small helper _pick_preferred_model that selects the highest‑capacity model still available.
def _pick_preferred_model(self, models: list[str]) -> str:
# Preference order: 120b > 70b > 40b > 20b
preference = ["gpt-oss-120b", "gpt-oss-70b", "gpt-oss-40b", "gpt-oss-20b"]
for pref in preference:
if pref in models:
return pref
raise RuntimeError("No compatible Groq model found")
During initialization of ContentGenerator, we now run the detection flow:
class ContentGenerator:
def __init__(self):
self.available_models, self.has_deprecated = self._list_available_models()
if not self.has_deprecated:
missing = list(DEPRECATED_MODELS - set(self.available_models))
self._send_deprecation_alert(missing)
self.model = self._pick_preferred_model(self.available_models)
# rest of init...
If the deprecated model is absent, the alert fires, and the generator automatically picks gpt‑oss‑120b. This eliminates the need for manual config changes and guarantees that we always use the most capable model we have access to.
3. Updating the CI Pipeline
The CI job (.github/workflows/content.yml) now sets the required environment variables for the alert email. I added a step that verifies the secret ALERT_SENDER exists before the job runs, failing fast with a clear message:
- name: Verify alert secrets
run: |
if [ -z "${{ secrets.ALERT_SENDER }}" ]; then
echo "Missing ALERT_SENDER secret"
exit 1
fi
4. Documentation
I updated docs/CLAUDE.md and docs/CLAUDE_CODE_CONTEXT.md (see content/2026/08/03/VS/changelog.md) to reflect the new model table and added a “Model deprecation handling” section. The changelog entry now reads:
## 2026‑08‑03 VS
### Changed
- `src/content_generator.py` – Added deprecation alert email and auto‑migration to gpt‑oss‑120b.
- `docs/CLAUDE.md` – Updated version table; fixed truncated “Last commit” field.
Key Takeaway
Never assume an external API’s contract is immutable. By treating model availability as a dynamic configuration and coupling it with an automated alert, you turn a silent breaking change into a visible, actionable event. The pattern—fetch‑validate‑alert‑fallback—can be reused for any third‑party service that may deprecate resources.
What’s Next
- Retry logic: Add exponential back‑off for the model list request to handle transient network hiccups.
- Feature flag: Expose the preferred‑model list via an environment variable so teams can opt‑in to experimental models without code changes.
- Dashboard: Push deprecation alerts to a Slack channel and store them in a small SQLite log for historical analysis.
Tags: #vibecoding #buildinpublic #python #automation #ai #email #groq #gpt-oss
Roberto Luna Osorio – Full Stack Developer & Project Lead
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/content-automation · 2026-08-04
#playadev #buildinpublic
Top comments (0)