Skip to content

AI review intermittently publishes no narrative summary (~40% of reviews) — root cause unknown #9432

Description

@JSONbored

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 authclaude 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 budgetAI_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.

Metadata

Metadata

Assignees

Labels

gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.maintainer-onlyOwner-only work — yields no Gittensor points.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions