fix(task-board): a review cycle always ends in a verdict or a person - #6688
Merged
Conversation
added 2 commits
August 28, 2026 14:16
…nt attempt A reviewer run that finishes without calling the decision tool - it ran out of turns, or stopped while waiting on a background task - left the card stranded In Review at 0/1 forever: `reviewerHandledThisCycle` read any completed thread as a review, so nothing re-dispatched it, and its attempt counted as unspent, so nothing handed it to a human either. Spentness now asks the cycle's timeline for a verdict instead of trusting the thread's terminal status. A completed-but-undecided attempt re-dispatches within the existing `MAX_REVIEWER_ATTEMPTS` budget and hands over when it runs out.
A reviewer run that finishes without calling TASK_BOARD_REVIEW_DECISION is now asked for one on its own thread, once per attempt, recorded on the timeline as `review_verdict_requested` so the ask survives a restart and can only happen once. Prompt says: decide on what you know, request_changes if you could not finish. If it still ends undecided the attempt is spent, so the existing budget takes over — a second reviewer, then the hand-to-human path. Every review cycle now terminates in a verdict or a person.
pedrofrxncx
force-pushed
the
fix/reviewer-completed-without-verdict
branch
from
August 28, 2026 17:17
aa25350 to
cb35aa4
Compare
pedrofrxncx
enabled auto-merge (squash)
August 28, 2026 17:19
decocms Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
PR: #6688 fix(task-board): a review cycle always ends in a verdict or a person Bump type: patch - decocms (apps/api/package.json): 4.297.3 -> 4.297.4 - @decocms/native (apps/native/package.json): 4.297.3 -> 4.297.4 - @decocms/shared (packages/shared/package.json): 0.71.1 -> 0.71.2 Deploy-Scope: both
pedrofrxncx
added a commit
that referenced
this pull request
Aug 31, 2026
…6688) * fix(task-board): a reviewer that completes without a verdict is a spent attempt A reviewer run that finishes without calling the decision tool - it ran out of turns, or stopped while waiting on a background task - left the card stranded In Review at 0/1 forever: `reviewerHandledThisCycle` read any completed thread as a review, so nothing re-dispatched it, and its attempt counted as unspent, so nothing handed it to a human either. Spentness now asks the cycle's timeline for a verdict instead of trusting the thread's terminal status. A completed-but-undecided attempt re-dispatches within the existing `MAX_REVIEWER_ATTEMPTS` budget and hands over when it runs out. * fix(task-board): ask a reviewer that ended undecided for its verdict A reviewer run that finishes without calling TASK_BOARD_REVIEW_DECISION is now asked for one on its own thread, once per attempt, recorded on the timeline as `review_verdict_requested` so the ask survives a restart and can only happen once. Prompt says: decide on what you know, request_changes if you could not finish. If it still ends undecided the attempt is spent, so the existing budget takes over — a second reviewer, then the hand-to-human path. Every review cycle now terminates in a verdict or a person. --------- Co-authored-by: Pedro França <pedrofrxncx@deco.cx>
pedrofrxncx
pushed a commit
that referenced
this pull request
Aug 31, 2026
PR: #6688 fix(task-board): a review cycle always ends in a verdict or a person Bump type: patch - decocms (apps/api/package.json): 4.297.3 -> 4.297.4 - @decocms/native (apps/native/package.json): 4.297.3 -> 4.297.4 - @decocms/shared (packages/shared/package.json): 0.71.1 -> 0.71.2 Deploy-Scope: both
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Prod, org
osklen, card OS-303 ("[GLOBAL][PERF] Habilitar cache de borda"): the reviewer thread reachedcompletedand the card shows a red 0/1 with nothing able to move it.The run never recorded a decision — its last parts are "Standing by for the background deno type-check…" and then
finish. Noreview_approved/review_changes_requestedon the timeline.From there the card is a dead end:
reviewerHandledThisCycletreats any non-failed terminal thread as a review ("a reviewer records its decision and completes"), so neither the sweeper nor the dialog poll re-dispatches.isSpentAttemptcounts only failed/hung threads, soreviewerAttemptsExhaustedis false and the hand-to-human path never fires.parkReviewedCardForHuman), so after feat(task-board): keep a card In Progress while its reviewer runs #6689 this card doesn't even sit in the reviewers' lane — it stays In Progress with an open review cycle, indefinitely, which is a quieter version of the same strand.The board's
0/1is correct throughout. There genuinely is no verdict. It just never changes.Fix — three rungs, so the cycle always terminates
nudgeThreadTurn, the mechanismensureReviewerCommentedalready uses) — its session still holds everything it reviewed, so this is cheaper and better-informed than a fresh reviewer. The prompt is narrow: record the verdict you already reached,request_changesif you could not finish, do not re-review, do not wait on background tasks.isSpentAttemptnow counts it:verdictRecorded(fromreviewCycleVerdictson the current cycle) replaces "terminal and not failed" as the proof of a review. The existingMAX_REVIEWER_ATTEMPTSbudget dispatches a second reviewer.reviewerAttemptsExhausted→handTaskToHumanpath, which parks the card In Review, unassigned.The ask happens at most once per thread, fenced on the timeline (
review_verdict_requested, migration 191) rather than in memory, so it survives a pod restart. An unanswered ask holds the attempt budget back forVERDICT_NUDGE_GRACE_MS(10 min), so the follow-up run's queue wait can't be mistaken for another silent completion.The reviewer prompt also now says outright that ending without a decision is the one failure the run cannot leave behind.
Note on the extra read
enqueueEnabledReviewersnow always reads the activity list. #6689 made the cycle boundary a column precisely to avoid that read — but the verdicts and the verdict-asks only exist on the timeline, so this path needs it regardless; the boundary still comes fromreviewCycleStartedAt, withreviewCycleStart's activity fallback for pre-190 cards. One indexed query per swept card per 5 min, which is what this path cost before #6689.Testing
bun test apps/api/src/tools/task-board/enqueue-reviewer.test.ts— 42 pass, 8 new cases (undecided → asked once, failed/live never asked, timeline-scoped asks, grace window, asked-then-still-undecided → spent → hand-off).activity-actions.test.tsre-pointed at migration 191;notification-types.test.tsstill holds — the new action is deliberately not a notification type, soNOTIFIED_ACTIONSkeeps it out of the inbox.bun run checkandbun run lintclean. The 10 remaining task-board test failures are pre-existing onmain(they need Postgres).Needs
bun run migrateon deploy (CHECK constraint swap only).