Working Paper · No. 04 · v0.2 (Mode 5 added)
Five hidden failure modes in AEO/GEO measurement pipelines
Lessons from a 7-day pre-registered experiment: why stable-looking citation rates are often artefacts.
Abstract
A 7-day pre-registered AEO experiment revealed five systematic failure modes in standard LLM citation measurement pipelines — each capable of producing artificially stable false-positive "deterministic" citation rates or artificially low aggregate hit rates. We document each failure mode with a reproduction recipe, a detection heuristic, and a fix. Combined effect: the pre-fix pipeline reported a stable 2–13 % citation rate across 41 of 44 historical runs (later identified as an artefact of misclassified errors); the post-fix pipeline yields 14–22 % over the same time window with correct error handling. Mean retroactive correction: +10.64 pp.
1
Background
GEO/AEO research increasingly relies on automated multi-LLM probes (Profound, BrightEdge AEO, ScrunchAI, ChatRank, etc.). Most tools do not expose their temperature settings, provider-specific config edge cases, rate-limit behaviour, or timeout heuristics. We set out five such failure modes that we found in our own pipeline (Cloudflare Workers + Anthropic + OpenAI + Gemini + Workers AI) — and argue that these are systemic, not implementation-specific.
2
Mode 1 — Workers AI default temperature = 0 (the determinism trap)
Symptom: citation rate identical to the second decimal place across 3 consecutive daily runs.
Root cause: Cloudflare Workers AI env.AI.run() defaults
to temperature: 0 when not set explicitly. With deterministic
decoding: same question → same answer → same score. The pipeline
correctly probed 5 Workers AI models daily, but each produced
byte-identical output.
Why this matters for AEO: determinism is invisible in scorecards. A stable 9 % is not a "finding" but an artefact.
Fix: explicit temperature: 0.5 in askWorkersAi(env, model, q).
Post-fix variance: ±2.3 pp day-to-day.
3
Mode 2 — OpenAI Search Preview rejects temperature
Symptom: the openai_search stage returns HTTP 400 — temperature is incompatible with search-preview models.
Root cause: gpt-4o-mini-search-preview-2025-03-11 has
a different parameter contract than the base model. It rejects temperature,
top_p, and several other sampling parameters.
Fix: conditional if (!isSearchPreview) body.temperature = 0.3; in askOpenAI().
4
Mode 3 — Anthropic Tier-1 rate limit (5 RPM) causes 16 parallel timeouts
Symptom: all 16 Claude probes time out simultaneously when called in parallel.
Root cause: Anthropic Tier-1 enforces 5 requests per minute. Sixteen parallel fetch() calls to /v1/messages cause requests 6–16 to queue server-side past the 45-second Cloudflare sub-request timeout.
Fix: the Anthropic Message Batches API (/v1/messages/batches) — async submit, max 24h delivery, −50 % cost discount. The 16 × 3 Claude tiers = 48 prompts are submitted in a single batch, polled hourly via a separate cron, and the results persisted retroactively into the snapshot.
The identical pattern applies to the Gemini Direct Batch API (from v2.8) and replaces the sync path for both providers.
5
Mode 4 — sub-request timeout too tight for long-tail probes
Symptom: ~5 % of probes return AbortError: The operation was aborted despite a valid provider response.
Root cause: the Cloudflare Worker sub-request timeout was set globally to 45 s. Workers AI Llama-3.1-8b on long-tail questions occasionally responds at 46–48 s. The Worker aborted the connection before the provider had finished streaming.
Fix: CHUNK_TIMEOUT_MS 24 s → 50 s. Trade-off: slightly higher Worker CPU cost, in return for eliminating the 5 % silent-failure rate.
6
Mode 5 — implicit score dilution through misclassified errors most impactful
Symptom: aggregate citation hit rate of 2–13 % over 4 days, even though per-LLM inspection shows individual LLMs at 14–28 %.
Root cause: when a provider API call fails (quota exhaustion, HTTP 5xx, network timeout), the standard pipeline pattern is:
async function askProvider(env, q) {
try {
const r = await fetch(...);
return r.ok ? await r.json() : null; // ← silent failure
} catch (e) {
return null; // ← silent failure
}
}
The downstream code receives null, passes it to scoreAnswer(''), which returns { score: 0, status: 'not_found' }. The row is persisted as a legitimate "not_found" with score=0. The aggregate calculation treats it identically to a genuine "LLM honestly says don't know" answer — both contribute 0/3 to the percentage calculation.
For one LLM out of eleven this is invisible (one row × 16 questions = −48 max points from both numerator and denominator, which cancels in the percentage calculation). For five LLMs down simultaneously (e.g. Workers AI quota fully exhausted), the dilution is severe:
| Scenario | Score sum | Max possible | Reported % | True % |
|---|---|---|---|---|
| 11 LLMs × 16 Q, ~14 % hit on 6 measurable, 5 dead | ~40 | 528 (= 11 × 16 × 3) | 7.6 % | 27.8 % (= 40/144) |
| 9 LLMs × 16 Q, ~15 % hit on 7 measurable, 2 dead | ~50 | 432 | 11.6 % | 14.9 % (= 50/336) |
Detection: inspect the raw answer_excerpt field. Genuine "not_found" answers contain LLM-generated text. Misclassified errors have an empty answer_excerpt plus status='not_found'. The combination is the fingerprint.
Fix:
- Remove silent error catching: let API errors propagate into the outer chunk runner, so that
status='error'(not'not_found') is captured. - Aggregate filter: in
reaggregateSnapshot(), excludestatus='error'rows from BOTH numerator and denominator. - Per-LLM transparency: report
n_attempted+n_legit+n_errorper LLM. Pure-error LLMs (n_legit=0) are givenstatus: 'unavailable'. - Retroactive correction with versioning: historical snapshots remain in legacy columns (
score_percent); new, methodologically correct values are stored in parallel columns (score_percent_v271). Both remain visible in the time series for a full audit trail.
Empirical effect in our pipeline (44 runs, 2026-05-10 to 2026-05-17):
| Period | Affected runs | Mean v2.0 | Mean v2.7.1 | Mean Δ |
|---|---|---|---|---|
| 2026-05-13 (Claude not yet in production) | 9 | 8.6 % | 21.9 % | +13.3 pp |
| 2026-05-14 to 2026-05-15 morning (Workers AI down) | 14 | 2.2 % | 17.4 % | +15.2 pp |
| 2026-05-15 evening to 2026-05-17 morning (Claude + OpenAI Search edge case) | 13 | 9.5 % | 15.4 % | +5.9 pp |
| 2026-05-17 evening (Gemini sync failed) | 5 | 9.9 % | 16.2 % | +6.3 pp |
| Total | 41/44 | 6.7 % | 17.4 % | +10.6 pp |
The pre-fix narrative "LLM citation for a pre-launch author stabilised at 9–13 %" was systematically under-reported by 5–15 pp. The corrected narrative is "LLM citation across measurable LLMs averaged between 14 and 22 % in the first week, with per-LLM noise driven primarily by provider availability rather than entity-recognition variance."
Detection heuristic for AEO tooling auditors: inspect the answer_excerpt distribution per LLM per run. If >50 % of rows for a given LLM in a given run have an empty answer_excerpt with status='not_found', assume API-call-failure misclassification until proven otherwise. Industry tools that do not expose raw answer_excerpt values may have silently distributed this bug across their entire customer base.
7
Six best-practice recommendations for AEO tooling builders
- Default temperature ≥ 0.3 on every provider, every call. Document the value in the scorecard methodology.
- Test each provider's parameter contract individually before scaling. A compatibility test is the unit test for AEO.
- Use provider batch APIs for high-volume probes. API Batches, the Gemini Batch API, and OpenAI Batch give −50 % cost + zero rate-limit friction. Async ≠ slow when you do daily collects.
- Calibrate timeouts on p99 response time, not p50. Long-tail probes will burn you.
- Treat zero-variance result sets as suspect. Determinism in AEO is almost always a bug, not a property.
- Treat an empty answer_excerpt + status='not_found' as suspect, not legitimate. Genuine LLM "don't know" answers contain LLM-generated text. Empty answers with a not_found classification are misclassified API errors. Audit the raw
answer_excerptdistribution per LLM per run — anything > 50 % empty strings is a smoking gun for Mode 5 dilution.
8
Limitations
- Single-pipeline observation (Cloudflare Workers); may not generalise to other serverless platforms.
- Provider behaviour changes over time — these specific bugs may be patched provider-side by the time of publication.
- We did not test other batch APIs (Vertex Direct, OpenAI Batch) — extrapolating to them is conjecture.
9
Replication
- Code: github.com/marintkael/marin-research-tools (MIT)
- Methodology Note 01 v2.8: DOI 10.5281/zenodo.20308495
- Pre-registration: DOI 10.5281/zenodo.20125967
- Companion paper: Working Paper 02 · Pre-Launch LLM Citation
- Living engineering journal: Project challenges and solutions — all pipeline bugs in full detail, including symptom + root cause + solution per entry
10
How to cite
Kael, M. T. (2026). Five hidden failure modes in AEO/GEO measurement pipelines: lessons from a 7-day pre-registered experiment. Working Paper 04 v0.2, Marin T. Kael — KI-Zitations-Feldlabor. Status: outline. URL: marin-t-kael.de/en/research/working-papers/wp-04-five-failure-modes.