Summary
Some AI reviews publish a real narrative summary; others publish the generic placeholder ("The AI review returned non-blocking notes for this change but did not include a separate narrative summary") or "AI review is unavailable for this PR head" — under what appears to be identical configuration. Root cause is not established.
Evidence (2026-07-27, all after AI_MAX_OUTPUT_TOKENS was restored to 4096)
| PR |
cached summary state |
time (UTC) |
| loopover#9426 |
REAL_SUMMARY |
18:21 |
| awesome-claude#5599 |
UNAVAILABLE |
18:20 |
| loopover#9423 |
REAL_SUMMARY |
18:03 |
| loopover#9417 |
FALLBACK_PLACEHOLDER |
18:01 |
| loopover#9420 |
REAL_SUMMARY |
17:58 |
| loopover#9415 |
REAL_SUMMARY |
17:56 |
| loopover#9422 |
FALLBACK_PLACEHOLDER |
17:55 |
| loopover#9421 |
FALLBACK_PLACEHOLDER |
17:49 |
Roughly 40% degraded. Query used:
SELECT repo_full_name, pull_number, created_at,
CASE WHEN notes LIKE 'AI review is unavailable%' THEN 'UNAVAILABLE'
WHEN notes LIKE 'The AI review returned%did not include a separate narrative%' THEN 'FALLBACK_PLACEHOLDER'
ELSE 'REAL_SUMMARY' END AS summary_state
FROM ai_review_cache WHERE created_at > '2026-07-27T17:37:00Z' ORDER BY created_at DESC;
Ruled out
- Provider auth —
claude CLI in the container works when invoked as the app does (CLAUDE_CODE_OAUTH_TOKEN sourced from /run/secrets/claude_code_oauth_token → returns OK). A bare docker exec claude -p ... reports "Not logged in", but that is a test artifact: loadFileSecrets populates process.env inside the Node process at boot and docker exec gets a fresh shell.
- Output token budget —
AI_MAX_OUTPUT_TOKENS was transiently set to 1024 (see #ISSUE2), which throttled ai-review.ts's own 4096 default and caused degraded reviews between ~16:24–17:36 UTC. Restored to 4096; degradation persists for a subset afterwards, so this is not the whole story.
- Stale cache replay — the re-run checkbox sets
forceAiReview: true, and processors.ts explicitly bypasses the cache (webhook.forceAiReview === true ? null : getCachedAiReview(...)). Re-runs do make a fresh LLM call.
- Bare-
score public-safe filter — a real contributing cause, now fixed separately (see below), but it does not explain PRs that succeed and fail in the same window.
Suspected but unverified
composeAdvisoryNotes reaching the UNAVAILABLE branch means it returned null and composeFallbackAdvisoryNotes returned null — i.e. no assessment, no blockers, no nits parsed at all. For awesome-claude#5599 this reproduced across two consecutive forced re-runs while ai_usage_events recorded status=ok, output_tokens 413–442. So the provider returned output that failed to parse into the expected structure. Worth checking whether parseModelReview is silently dropping a valid-but-differently-shaped response, and whether small PRs elicit a response shape the parser does not handle.
Why this matters
gate.aiReview.mode: block is set for these repos. A review with no usable assessment routes to a manual-review hold, so this directly stalls the merge pipeline.
Related
- Public-score-terms allowlist was unset in production, which silently discarded whole narrative summaries for any review using the word "score"/"scoring". Fixed by setting
LOOPOVER_PUBLIC_SCORE_TERMS_ALLOWED_REPOS and by the preflight in the linked PR.
- The no-narrative placeholder was being persisted into
ai_review_cache.notes and read back as a genuine assessment. Fixed in the linked PR.
Summary
Some AI reviews publish a real narrative summary; others publish the generic placeholder ("The AI review returned non-blocking notes for this change but did not include a separate narrative summary") or "AI review is unavailable for this PR head" — under what appears to be identical configuration. Root cause is not established.
Evidence (2026-07-27, all after
AI_MAX_OUTPUT_TOKENSwas restored to 4096)Roughly 40% degraded. Query used:
Ruled out
claudeCLI in the container works when invoked as the app does (CLAUDE_CODE_OAUTH_TOKENsourced from/run/secrets/claude_code_oauth_token→ returnsOK). A baredocker exec claude -p ...reports "Not logged in", but that is a test artifact:loadFileSecretspopulatesprocess.envinside the Node process at boot anddocker execgets a fresh shell.AI_MAX_OUTPUT_TOKENSwas transiently set to 1024 (see #ISSUE2), which throttledai-review.ts's own 4096 default and caused degraded reviews between ~16:24–17:36 UTC. Restored to 4096; degradation persists for a subset afterwards, so this is not the whole story.forceAiReview: true, andprocessors.tsexplicitly bypasses the cache (webhook.forceAiReview === true ? null : getCachedAiReview(...)). Re-runs do make a fresh LLM call.scorepublic-safe filter — a real contributing cause, now fixed separately (see below), but it does not explain PRs that succeed and fail in the same window.Suspected but unverified
composeAdvisoryNotesreaching theUNAVAILABLEbranch means it returnednullandcomposeFallbackAdvisoryNotesreturnednull— i.e. no assessment, no blockers, no nits parsed at all. For awesome-claude#5599 this reproduced across two consecutive forced re-runs whileai_usage_eventsrecordedstatus=ok,output_tokens413–442. So the provider returned output that failed to parse into the expected structure. Worth checking whetherparseModelReviewis silently dropping a valid-but-differently-shaped response, and whether small PRs elicit a response shape the parser does not handle.Why this matters
gate.aiReview.mode: blockis set for these repos. A review with no usable assessment routes to a manual-review hold, so this directly stalls the merge pipeline.Related
LOOPOVER_PUBLIC_SCORE_TERMS_ALLOWED_REPOSand by the preflight in the linked PR.ai_review_cache.notesand read back as a genuine assessment. Fixed in the linked PR.