Review Range Handling and Timeout Classification

Investigate why "entire review" in ../entire.io is failing "gemini ✗ failed 1m53s Entire CLI will link this conversati…"
Reproduced with just Gemini:
Root cause: Gemini CLI is failing due to quota exhaustion, not because of Entire’s hook message.
Key stderr:
The confusing line:
is just Entire’s hook message being printed first in stderr, so the review status table truncates there and makes it look like the cause.
I also saw prior Gemini transcript/log evidence of the same issue:
So the failure is external/API quota for gemini-3.5-flash. No repo/code issue in ../entire.io found.
Subagent 'generalist' failed: TerminalQuotaError: You have exhausted your daily quota on this model. at classifyGoogleError (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:304428:16) at retryWithBackoff (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:305113:31) at process.processTicksAndRejections (node:internal/process/task_queues:104:5) at async GeminiChat.makeApiCallAndProcessStream (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:328353:28) at async GeminiChat.streamWithRetries (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:328171:29) at async _LocalAgentExecutor.callModel (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:344994:22) at async _LocalAgentExecutor.executeTurn (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:344563:43) at async _LocalAgentExecutor.runInternal (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:344775:30) at async LocalSubagentInvocation.execute (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:345612:22) at async file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:349110:23 { cause: { code: 429, message: 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n' + '* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-3.5-flash\n' + 'Please retry in 53.251013646s.', details: [ [Object], [Object], [Object] ] }, retryDelayMs: undefined, reason: undefined } Attempt 1 failed: You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit.
- Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 5, model: gemini-3.5-flash Please retry in 52.971308879s. Suggested retry after 52s.. Retrying after 62315ms... Error when talking to Gemini API Full report available at: /var/folders/xf/nyhb2vjd3ps7f3rh_g4tg30r0000gn/T/gemini-client-error-Turn.run-sendMessageStream-2026-06-19T15-42-09-638Z.json TerminalQuotaError: You have exhausted your daily quota on this model. at classifyGoogleError (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:304428:16) at retryWithBackoff (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:305113:31) at process.processTicksAndRejections (node:internal/process/task_queues:104:5) at async GeminiChat.makeApiCallAndProcessStream (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:328353:28) at async GeminiChat.streamWithRetries (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:328171:29) at async Turn.run (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:328917:24) at async GeminiClient.processTurn (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:342407:22) at async GeminiClient.sendMessageStream (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/chunk-SBG6CUNK.js:342504:14) at async file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/gemini-PDNY7O5B.js:10882:26 at async main (file:///opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/gemini-PDNY7O5B.js:16290:5) { cause: { code: 429, message: 'You exceeded your current quota, please check your plan and billing details. For more information on this error, head to: https://ai.google.dev/gemini-api/docs/rate-limits. To monitor your current usage, head to: https://ai.dev/rate-limit. \n' + '* Quota exceeded for metric: generativelanguage.googleapis.com/generate_content_free_tier_requests, limit: 20, model: gemini-3.5-flash\n' + 'Please retry in 50.368881605s.', details: [ [Object], [Object], [Object] ] }, retryDelayMs: undefined, reason: undefined } An unexpected critical error occurred:[object Object] is this also related?
Yes — it’s the same underlying issue.
What’s happening:
- Gemini starts the review.
- Gemini invokes its local subagent:
generalist. - That subagent also uses
gemini-3.5-flash. - The API returns
429quota exceeded. - Gemini CLI retries after ~52–62s.
- The next top-level Gemini turn also hits quota and exits with status 1.
- Entire then reports Gemini review as failed.
The important lines are:
and:
The limit: 5 vs limit: 20 likely reflects different Gemini quota buckets/metrics, but both are quota failures for the same model.
The A trail ties together... / Entire hook messages are unrelated normal Entire output. They’re not the cause.
Fix options:
- wait for Gemini quota reset / retry later
- use a paid Gemini API key or higher quota project
- switch Gemini to another model if available
- remove/skip Gemini from the Entire review profile for now
- run only non-Gemini reviewers until quota recovers
But why is the reviewer agent eventually failing the whole thing? It could just fail silently or raise the error again on the final report (just terminal print, not in the report or findings).
Yes, related — and the reason it “fails the whole thing” is in Entire CLI’s review orchestration policy.
Current behavior:
- Reviewer process exits non-zero.
RunMulti(...)marks that reviewer asfailed.RunMulti(...)still lets siblings and the judge proceed.- But it returns the first non-cancelled agent error as
waitErr. runMultiAgentPath(...)posts/writes whatever it can, then returns:
So the review can complete partially, but the CLI command exits non-zero.
Relevant code:
-
cmd/entire/cli/review/run_multi.go- comment says it returns “the FIRST non-cancellation per-agent error”
firstErris set from failed reviewer errorsreturn summary, firstErr
-
cmd/entire/cli/review/cmd.go- after manifest/trail posting:
So this is intentional-ish: failed reviewer infrastructure is surfaced as command failure, even though the failed reviewer’s output is not included in the judge report/findings.
Your expected behavior is reasonable: for multi-agent review, a reviewer quota/auth/tool failure could be treated as a terminal-only warning unless every reviewer failed or there is no final report.
A likely policy change would be:
- still mark Gemini failed in terminal/TUI
- exclude Gemini failure from synthesis/finding output
- return exit 0 if at least one reviewer succeeded and/or a judge verdict was produced
- return exit non-zero only if all reviewers failed/cancelled or judge/posting failed
For --agent gemini single-agent mode, it should probably still exit non-zero, because there is no review result.
Nah, that's shit. Let's fix it. Only if all reviewers fail we can abort early, otherwise pull it through.
Implemented.
Changes:
- Multi-agent review now only fails the command if no reviewer succeeds.
- A failed reviewer is still shown in terminal output/counts.
- Failed reviewer output is excluded from:
- judge synthesis prompt
- trail fallback findings/report body
- If all reviewers fail, command returns
review run: ...before manifest/trail posting.
Touched:
cmd/entire/cli/review/cmd.gocmd/entire/cli/review/synthesis_prompt.gocmd/entire/cli/review/synthesis_sink.go- tests in
cmd/entire/cli/review/*_test.go
Validated:
Checkout the last finding on this trail https://entire.io/gh/entirehq/entire.io/trails/1421/ it's the last entire review session in ../entire.io that posted it as one finding instead of individuals. I thought this was fixed but apparently it's not? Investigate.
Investigated and fixed.
Root cause:
- Latest trail finding
019ee098-da2b...was posted from local manifest:../entire.io/.git/entire-review/manifests/20260619T154957-2ea39525-08ed-4604-bc06-b18d3d56b645.json
- Its
aggregate_outputwas:
- The trail-post splitter only handled:
- runner JSON last-line format
- top-level markdown bullets:
- ...,1. ...
So because the judge emitted top-level bold severity paragraphs instead of bullets, splitReviewVerdictFindings returned no items, and Entire posted the whole verdict as one Review verdict.
Fixes made:
cmd/entire/cli/review_bridge.go- split top-level severity-marked findings like:
**[HIGH] ...[medium] ...HIGH — ...
- changed single detected finding to post as a finding, not a verdict.
- location inference now works for backticked paths like:
`api/src/lib/planetscale/trails.ts:657–668`
- split top-level severity-marked findings like:
Tests added:
cmd/entire/cli/review_bridge_test.go- verifies bold severity blocks split into individual trail findings
- verifies severity + location inference
Validated:
Also kept the earlier fix in place: multi-agent review only aborts if all reviewers fail.
This works fine with just the review runner we have in place. Can we be a bit more explicit here for both reviewers and consolidation agent so that they don't fuck it up?
Done.
Added explicit output-format instructions for:
-
Reviewer agents (
cmd/entire/cli/review/prompt.go)- verdict line first
- each finding as separate top-level bullet
- bullets must start
[high],[medium], or[low] - no combined defects
- no bold severity-heading paragraphs without bullets
-
Consolidation/judge agent (
cmd/entire/cli/review/synthesis_prompt.go)- same rules, stricter:
- exact bullet shape:
- [high] file:line — bug; impact; fix. - no headings / numbered sections / grouped severity sections
- one defect per bullet
- exact bullet shape:
- same rules, stricter:
Added tests for both prompt changes.
Validated:
Entire Trail review comment ID: 019eefd4-45d7-7f0e-8824-40bf3f54c91f URL: https://entire.io/gh/entireio/cli/trails/466/review-profiles Severity: low Context: cmd/entire/cli/review/run_multi.go run_multi.go:~162 — Start-phase deadline classified as a generic failure, not a timeout
When r.Start(agentCtx, cfg) returns because the per-agent deadline fired, the queued Start-failure terminal sets startErr/finishedAt but never sets timedOut. In the final loop, classifyStatus reports AgentStatusFailed with the raw "context deadline exceeded" instead of the friendly timedOutError ("review agent X timed out after Y"). The post-Start path classifies the same condition correctly via reviewerDeadlineFired, so the two phases are inconsistent.
Reachable only if an agent Start blocks until the deadline (most spawn quickly), hence low severity.
Suggested fix: detect a reviewer-deadline cause on the Start error (reviewerDeadlineFired / errors.Is(..., errReviewerTimeoutCause)) and set terminal.timedOut before queueing the Start-failure marker.
Fixed and pushed.
Commit:
What changed:
- Start-phase reviewer deadline now sets
timedOutinRunMulti. - Single-agent
Runalso reports the friendly timeout error for Start-time deadline expiry. - Added tests for Start-time timeout classification in both paths.
Validated:
Entire Trail review comment ID: 019eefd4-7989-7dc1-83c1-5a9401d61dad URL: https://entire.io/gh/entireio/cli/trails/466/review-profiles Severity: low Context: cmd/entire/cli/review/run_multi.go run_multi.go:~178 — shouldEmitSyntheticRunError is passed the parent ctx, not the per-agent runCtx
The terminal timedOut classification correctly uses both contexts (reviewerDeadlineFired(ctx, runCtx, waitErr)), but the synthetic-error guard only sees the parent ctx. For an adapter whose Wait returns a string-formatted deadline error (not %w-wrapped — exactly the case reviewerDeadlineFired has a strings.Contains fallback for), a per-agent timeout would both emit a synthetic RunError to the sinks AND be classified as timedOut, i.e. the timeout is double-reported.
Low severity, contingent on adapter error formatting.
Suggested fix: pass runCtx to shouldEmitSyntheticRunError so its ctx.Err() != nil guard also suppresses on a per-agent deadline/cancel.
Fixed and pushed.
Commit:
Change:
shouldEmitSyntheticRunErrornow receives the per-agent context in both single and multi-agent paths.- This suppresses synthetic
RunErroremission when the per-reviewer timeout/cancel context is already done, avoiding duplicate timeout reporting. - Added assertions for string-formatted deadline errors in both
RunandRunMulti.
Validated:
Entire Trail review comment ID: 019eefd4-a4a2-7ae6-aa2c-db2c50ddb68f URL: https://entire.io/gh/entireio/cli/trails/466/review-profiles Severity: low Context: cmd/entire/cli/review/profile.go profile.go — three near-duplicate "sort map keys" helpers
sortedProfileNames (~129), sortedProfileAgentNames (~150), and sortedReviewConfigKeys (~398) each extract keys from a map and return them sorted. The per-reviewer "if cfg.Model { append flag }" block is also repeated across the claudecode/codex/geminicli reviewers.
This is cleanup, not a bug, but the repo dupl linter (threshold 75) may flag it.
Suggested fix: collapse the key-sorting helpers into a single generic helper (e.g. slices.Sorted(maps.Keys(m))) and factor the model-flag append into one shared reviewer helper.
Addressed and pushed.
Commit:
Changes:
- Collapsed profile key sorting into one generic
sortedMapKeys. - Added shared
review.AppendModelFlag. - Updated claude-code/codex/gemini reviewers to use it.
Validated:
Entire Trail review comment ID: 019eefdd-797a-7954-aee5-ef3fc46fa843 URL: https://entire.io/gh/entireio/cli/trails/466/review-profiles Severity: low Context: cmd/entire/cli/review_bridge.go:176 Selected context:
The validation loc.EndLine >= loc.StartLine allows EndLine to equal StartLine for a range granularity, which semantically represents a single line but uses range syntax. While this may be intentional for API flexibility, it could lead to confusing range markers in the UI (e.g., "lines 42-42"). Consider either requiring loc.EndLine > loc.StartLine for true ranges, or explicitly documenting that single-line ranges are valid and how they should be rendered.
Fixed and pushed.
Commit:
Change:
- JSON
rangelocations withend_line == start_linenow normalize tolinegranularity. - True ranges still require
end_line > start_line. - Added regression test.
Validated:
Entire Trail review comment ID: 019eefe7-341c-7815-8d07-3108156b1fbd URL: https://entire.io/gh/entireio/cli/trails/466/review-profiles Severity: medium Context: cmd/entire/cli/review_bridge.go:162 Selected context:
When reviewTrailLocationFromJSON processes a range with EndLine < StartLine (invalid range), it silently falls through to return whole_change granularity instead of either rejecting the input or normalizing to a valid single-line location. This could mask reviewer bugs where line ranges are accidentally inverted, causing findings to lose their precise location anchoring.
Fixed and pushed.
Commit:
Change:
- Invalid JSON
rangelocations with a validfile_pathandstart_linenow normalize tolinegranularity atstart_lineinstead of falling back towhole_change. - Added regression test for inverted ranges.
Validated: