Skip to content

Commit b7d9ef8

Browse files
authored
fix: make registry sync atomic to prevent missing icons (#485)
The registry docs generation script silently swallowed icon fetch failures, causing auto-merged PRs to randomly drop agent icons when the CDN had transient errors. - Add retry logic (3 attempts with 2s delay) to icon fetching - Fetch all data (registry + icons) upfront before rendering - Abort with non-zero exit code if any icon fetch fails after retries
1 parent 31ddd6a commit b7d9ef8

1 file changed

Lines changed: 48 additions & 13 deletions

File tree

scripts/generate_registry_docs.py

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import json
1818
import os
1919
import re
20+
import sys
21+
import time
2022
import urllib.request
2123
from pathlib import Path
2224

@@ -103,18 +105,49 @@ def _fetch_registry() -> dict:
103105
return data
104106

105107

106-
def _fetch_icon_svg(agent_id: str) -> str:
107-
"""Fetch and sanitize SVG icon from CDN."""
108+
def _fetch_icon_svg(agent_id: str, retries: int = 3) -> str | None:
109+
"""Fetch and sanitize SVG icon from CDN.
110+
111+
Returns the sanitized SVG string, or None if all retries fail.
112+
"""
108113
url = f"{ICON_BASE_URL}/{agent_id}.svg"
109-
try:
110-
svg = _make_request(url, timeout=10).decode("utf-8")
111-
return _sanitize_svg(svg)
112-
except Exception as e:
113-
print(f"Warning: Could not fetch icon for {agent_id}: {e}")
114-
return ""
114+
for attempt in range(1, retries + 1):
115+
try:
116+
svg = _make_request(url, timeout=10).decode("utf-8")
117+
return _sanitize_svg(svg)
118+
except Exception as e:
119+
print(f"Warning: Could not fetch icon for {agent_id} (attempt {attempt}/{retries}): {e}")
120+
if attempt < retries:
121+
time.sleep(2)
122+
return None
123+
124+
125+
def _fetch_all_icons(agents: list[dict]) -> dict[str, str]:
126+
"""Fetch icons for all agents from the CDN.
127+
128+
Returns a dict mapping agent_id -> sanitized SVG string.
129+
Raises SystemExit if any icon fails to fetch after retries.
130+
"""
131+
icons: dict[str, str] = {}
132+
failed: list[str] = []
133+
134+
for agent in agents:
135+
agent_id = agent.get("id", "-")
136+
svg = _fetch_icon_svg(agent_id)
137+
if svg is None:
138+
failed.append(agent_id)
139+
else:
140+
icons[agent_id] = svg
115141

142+
if failed:
143+
print(f"Error: Failed to fetch icons for {len(failed)} agent(s): {', '.join(failed)}")
144+
print("Aborting to prevent publishing docs with missing icons.")
145+
sys.exit(1)
116146

117-
def _render_agent_cards(agents: list[dict]) -> str:
147+
return icons
148+
149+
150+
def _render_agent_cards(agents: list[dict], icons: dict[str, str]) -> str:
118151
"""Render agent cards as MDX components."""
119152
# Sort agents by name
120153
agents = sorted(agents, key=lambda a: a.get("name", "").lower())
@@ -126,7 +159,7 @@ def _render_agent_cards(agents: list[dict]) -> str:
126159
name = agent.get("name", agent_id)
127160
version = _escape_text(agent.get("version", "-"))
128161
repository = agent.get("repository", "")
129-
icon_svg = _fetch_icon_svg(agent_id)
162+
icon_svg = icons.get(agent_id)
130163

131164
lines.append(" <Card")
132165
lines.append(f' title="{_escape_html(name)}"')
@@ -158,16 +191,18 @@ def main() -> None:
158191
print("Please create the template file first.")
159192
raise SystemExit(1)
160193

161-
# Fetch registry data
194+
# Phase 1: Fetch all data (registry + icons) — abort on any failure
162195
registry = _fetch_registry()
163196
agents = registry.get("agents", [])
164197

165198
if not agents:
166199
print("Warning: No agents found in registry")
167200

168-
# Generate output
201+
icons = _fetch_all_icons(agents)
202+
203+
# Phase 2: Render and write — only reached if all fetches succeeded
169204
template = TEMPLATE_PATH.read_text()
170-
cards = _render_agent_cards(agents)
205+
cards = _render_agent_cards(agents, icons)
171206
output = template.replace(PLACEHOLDER, cards)
172207

173208
# Write output

0 commit comments

Comments
 (0)