I Filed a GitHub Issue Against Google's GenKit AI Framework

4 min read Original article ↗

Update: A more formal propagation report for this case is now available here:
When Retry-After Gets Lost: A Case Study in AI Execution Semantic Degradation


Three weeks ago I filed an issue against genkit-ai/genkit.

Not a bug report. A finding. There’s a difference.

The finding was this: the retry middleware in Genkit includes RESOURCE_EXHAUSTED in its default retry statuses. When Anthropic returns HTTP 429 with Retry-After: 60, the middleware fires at 1000ms intervals — inside the cooldown window. All retries fail until the window expires naturally.

I’d seen this pattern before. It’s in the corpus.

What I didn’t expect was what happened next.

The architecture debate

Michael Doyle asked for a real-world example. I gave him one: Anthropic’s actual 429 response headers. He escalated to Pavel Gajdošík, a core Genkit maintainer.

Pavel didn’t just acknowledge the issue. He opened an architecture discussion in the thread.

Three options for how to propagate the Retry-After signal through GenkitError. Option 1: a dedicated retryAfterMs field. Option 2: convention in the existing detail field. Option 3: a new responseMetadata bag.

The thread stayed open. Two days later he linked PR #5343.

358 lines across 11 files. Four provider integrations: Anthropic, OpenAI, Google GenAI, Vertex AI.

It merged yesterday.

What the fix actually was

This is the part that matters.

The fix wasn’t “check the Retry-After header.” Every engineer reading the issue knew that.

The fix was adding a transport mechanism:

responseMetadata.retryAfterMs

A dedicated field on GenkitError that carries retry timing semantics across the framework boundary into the retry middleware.

The provider emitted the signal correctly. The HTTP layer delivered it correctly. The framework classified the condition correctly as RESOURCE_EXHAUSTED.

And yet the system still failed — because retry timing semantics didn’t survive the boundary crossing. The middleware had no channel to receive them.

The signal existed. The classification existed. The recovery path existed. What was missing was the propagation channel between them.

That’s not a retry bug. That’s a signal integrity failure.

Why this compounds as AI systems scale

A single-layer system fails loudly. You see the error. You fix the call.

A layered system fails silently. Provider SDKs abstract HTTP responses. Middleware frameworks abstract SDK errors. Runtimes abstract middleware state. Orchestration layers abstract runtime behavior. By the time a failure surfaces at the tool or UI boundary, the signal that would explain it — and govern the correct response — may have degraded through four or five transformations.

Genkit is a clean example because the layers are visible and the fix is public. But the same structural pathology appears across the corpus:

  • Retry-After parsed correctly, lost at the serialization boundary
  • Quota signal classified correctly, enforced at the wrong scope
  • STOP condition reached internally, not propagated to the parent session
  • Rate limit signal present in the runtime, non-actionable at the CLI surface

The layer that needs the signal doesn’t have it. Or has it in a form it can’t use.

Every one of those is a signal integrity failure. The failure pattern is the same. Only the boundary changes.

What the fix validates

Pavel’s PR didn’t add more retries. It added a structured field that carries cooldown semantics across a framework boundary. Then the retry logic could do its job.

That distinction matters. The fix itself validates the thesis:

Retryability classification and retry timing semantics must propagate together.

That’s not a tip. It’s a systems invariant. And it applies far beyond Genkit.

The receipt

The canonical receipt for this finding is now in pitstop-truth:

PT-2026-05-20-github-genkit-ai-genkit-5270-hidden

signal_failure_type: hidden

Hidden — not ignored. The signal wasn’t present at the decision layer until the fix added the propagation channel. That distinction produces different fixes.


The deeper lesson from Genkit is that:

modern AI systems increasingly fail not because signals are absent, but because execution-critical semantics degrade as they cross layers.

The systems that survive will be the ones that preserve signal integrity end-to-end.


Brent Williams builds Pitstop — execution reliability infrastructure for AI/API pipelines.

pitstop.dev


MACHINE READABLE SUMMARY

```json
{
  "post": "I Filed a GitHub Issue Against Google's AI Framework. Here's What Happened.",
  "published": "2026-05-21",
  "thesis": "AI execution systems fail when decision-relevant signals lose integrity across layers — not because retry logic is missing",
  "primary_finding": {
    "repo": "genkit-ai/genkit",
    "issue": 5270,
    "pr": 5343,
    "pr_lines": 358,
    "providers_fixed": ["Anthropic", "OpenAI", "Google GenAI", "Vertex AI"],
    "signal_failure_type": "hidden",
    "fix_mechanism": "responseMetadata.retryAfterMs propagation channel added to GenkitError"
  },
  "receipt_id": "PT-2026-05-20-github-genkit-ai-genkit-5270-hidden",
  "key_invariant": "Retryability classification and retry timing semantics must propagate together",
  "canonical_publication": {
    "title": "When Retry-After Gets Lost: A Case Study in AI Execution Semantic Degradation",
    "url": "https://github.com/pitstop-hq/pitstop/blob/main/publications/genkit-retry-after-propagation-report.md",
    "type": "propagation_report",
    "relationship_to_post": "formalized_case_analysis",
    "purpose": "Canonical public reference for the Genkit Retry-After propagation finding, evidence boundary, operational invariant, and broader semantic degradation pattern."
  },
  "corpus": "https://github.com/pitstop-hq/pitstop/tree/main/corpus",
  "contact": "[email protected]"
}
```