feat(task-board): keep a card In Progress while its reviewer runs - #6689
Merged
Conversation
added 2 commits
August 28, 2026 13:09
A card moved to In Review the moment its PR was opened, so it sat in the reviewers' lane for the whole time an agent was still working on it. In Review read as "waiting on a person" when nobody was waiting on anything. The lane could not simply be changed, because the review CYCLE — the boundary that decides which reviewer verdicts still count — was derived from the newest `status_changed → in_review` activity. Moving a card mid review would have reset that boundary and invalidated its own reviewer's verdict, which is the failure mode that stranded 13 prod cards holding an approval that could never merge. So the cycle stops riding on the lane. `review_cycle_started_at` is a column (migration 189, backfilled from the timeline so in-flight cycles survive the deploy), and `reviewCycleStart` reads it, falling back to the old activity scan only for cards stamped before it. With that decoupled, the lane is free to say what a person wants to read: agent opens a PR → review cycle opens, card STAYS In Progress reviewer decides → In Review (`parkReviewedCardForHuman`) hand-off / retries → In Review, unassigned `inReviewPhase` is the predicate every automatic path now takes instead of `status === "in_review"`, and the sweeper's work list mirrors it. Two regressions that decoupling opens are closed explicitly: a failed REVIEWER run no longer schedules a Super Agent retry or parks the card on To Do (the reviewer has its own budget), and `closesOwnReview` gates on the phase, so an author's run can't mark its own work Done while a reviewer is still reading it. Verified against real Postgres: migration up/down from an empty database, the backfill against a seeded pre-migration card, and the full unit + integration suite with no new failures.
A card went to In Review and stayed there for the whole reviewer run — the exact thing this branch exists to prevent. `advanceLinkedTasksToReviewOnThreadFinish` reads `listPrs` to decide repo-backed vs repo-less. A run that links its PR moments AFTER its thread goes terminal is read as repo-less and parked In Review. The link then lands on a card that is no longer In Progress, so `openReviewCycleIfInProgress` matched nothing and the cycle never opened at all. Observed on a real board at 52 seconds between the two: 15:42:47 thread finished, listPrs empty -> card moved to In Review 15:43:39 PR #423 linked -> update matched nothing, no cycle 15:49:37 sweeper dispatched the reviewer via the in_review lane branch The card therefore read In Review while an agent reviewed it, and with `review_cycle_started_at` NULL every verdict fell back to the legacy activity scan. The predicate now also matches a Super-Agent-owned card sitting In Review with NO cycle open, and takes it back to In Progress in the same statement. That is safe precisely because the cycle is null: a card whose cycle never opened has had no reviewer and no verdict, so there is nothing behind it to invalidate. The Super Agent guard keeps it off a card a person has taken over (`handTaskToHuman` clears the assignee), and one statement means the two columns cannot interleave with the sweeper's read. Real-Postgres tests: the late-link rescue, an In Review card with a cycle already open left alone (a verdict put it there), a human-owned card left alone, and a shipped card left alone. The test that asserted the old narrow rule is inverted rather than appended.
pedrofrxncx
force-pushed
the
feat/review-cycle-off-lane
branch
from
August 28, 2026 16:09
e42cb0d to
1750b36
Compare
… repo column Three consecutive prod cards were parked In Review the moment their Super Agent run finished, on top of an already-open review cycle, and sat there for the whole reviewer run — the exact behaviour this branch removes. `advanceLinkedTasksToReviewOnThreadFinish` decided repo-backed vs repo-less with `item.repo != null && listPrs().length > 0`. The `repo` conjunct was a cheap way to skip the query for a card that could not have a pull request, but `repo` is only stamped on a card CREATED against a repository — a run that finds its own with `TASK_ADD_REPO` links a PR and leaves it null. Those cards read as repo-less forever, so the backstop took the "its answer IS its deliverable" branch and moved them. The fourth card, created against a repo, stayed In Progress exactly as intended, which is what made the pattern legible. A linked PR is the whole test now. And the backstop skips a card whose cycle is already open whatever the PR read says: only the repo-less branch can move a card, and moving one out from under its reviewer is the failure this exists to prevent. Real-Postgres tests for both: a PR-carrying card with a null repo stays In Progress with its cycle stamped, and a card with an open cycle is left where it is.
pedrofrxncx
enabled auto-merge (squash)
August 28, 2026 17:11
decocms Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
PR: #6689 feat(task-board): keep a card In Progress while its reviewer runs Bump type: minor - decocms (apps/api/package.json): 4.295.1 -> 4.296.0 - @decocms/native (apps/native/package.json): 4.295.1 -> 4.296.0 - @decocms/shared (packages/shared/package.json): 0.69.0 -> 0.70.0 Deploy-Scope: both
This was referenced Aug 28, 2026
pedrofrxncx
added a commit
that referenced
this pull request
Aug 31, 2026
) * feat(task-board): keep a card In Progress while its reviewer runs A card moved to In Review the moment its PR was opened, so it sat in the reviewers' lane for the whole time an agent was still working on it. In Review read as "waiting on a person" when nobody was waiting on anything. The lane could not simply be changed, because the review CYCLE — the boundary that decides which reviewer verdicts still count — was derived from the newest `status_changed → in_review` activity. Moving a card mid review would have reset that boundary and invalidated its own reviewer's verdict, which is the failure mode that stranded 13 prod cards holding an approval that could never merge. So the cycle stops riding on the lane. `review_cycle_started_at` is a column (migration 189, backfilled from the timeline so in-flight cycles survive the deploy), and `reviewCycleStart` reads it, falling back to the old activity scan only for cards stamped before it. With that decoupled, the lane is free to say what a person wants to read: agent opens a PR → review cycle opens, card STAYS In Progress reviewer decides → In Review (`parkReviewedCardForHuman`) hand-off / retries → In Review, unassigned `inReviewPhase` is the predicate every automatic path now takes instead of `status === "in_review"`, and the sweeper's work list mirrors it. Two regressions that decoupling opens are closed explicitly: a failed REVIEWER run no longer schedules a Super Agent retry or parks the card on To Do (the reviewer has its own budget), and `closesOwnReview` gates on the phase, so an author's run can't mark its own work Done while a reviewer is still reading it. Verified against real Postgres: migration up/down from an empty database, the backfill against a seeded pre-migration card, and the full unit + integration suite with no new failures. * fix(task-board): open the review cycle when the PR link loses the race A card went to In Review and stayed there for the whole reviewer run — the exact thing this branch exists to prevent. `advanceLinkedTasksToReviewOnThreadFinish` reads `listPrs` to decide repo-backed vs repo-less. A run that links its PR moments AFTER its thread goes terminal is read as repo-less and parked In Review. The link then lands on a card that is no longer In Progress, so `openReviewCycleIfInProgress` matched nothing and the cycle never opened at all. Observed on a real board at 52 seconds between the two: 15:42:47 thread finished, listPrs empty -> card moved to In Review 15:43:39 PR #423 linked -> update matched nothing, no cycle 15:49:37 sweeper dispatched the reviewer via the in_review lane branch The card therefore read In Review while an agent reviewed it, and with `review_cycle_started_at` NULL every verdict fell back to the legacy activity scan. The predicate now also matches a Super-Agent-owned card sitting In Review with NO cycle open, and takes it back to In Progress in the same statement. That is safe precisely because the cycle is null: a card whose cycle never opened has had no reviewer and no verdict, so there is nothing behind it to invalidate. The Super Agent guard keeps it off a card a person has taken over (`handTaskToHuman` clears the assignee), and one statement means the two columns cannot interleave with the sweeper's read. Real-Postgres tests: the late-link rescue, an In Review card with a cycle already open left alone (a verdict put it there), a human-owned card left alone, and a shipped card left alone. The test that asserted the old narrow rule is inverted rather than appended. * fix(task-board): a linked PR is what makes a card reviewable, not its repo column Three consecutive prod cards were parked In Review the moment their Super Agent run finished, on top of an already-open review cycle, and sat there for the whole reviewer run — the exact behaviour this branch removes. `advanceLinkedTasksToReviewOnThreadFinish` decided repo-backed vs repo-less with `item.repo != null && listPrs().length > 0`. The `repo` conjunct was a cheap way to skip the query for a card that could not have a pull request, but `repo` is only stamped on a card CREATED against a repository — a run that finds its own with `TASK_ADD_REPO` links a PR and leaves it null. Those cards read as repo-less forever, so the backstop took the "its answer IS its deliverable" branch and moved them. The fourth card, created against a repo, stayed In Progress exactly as intended, which is what made the pattern legible. A linked PR is the whole test now. And the backstop skips a card whose cycle is already open whatever the PR read says: only the repo-less branch can move a card, and moving one out from under its reviewer is the failure this exists to prevent. Real-Postgres tests for both: a PR-carrying card with a null repo stays In Progress with its cycle stamped, and a card with an open cycle is left where it is. --------- Co-authored-by: Pedro França <pedrofrxncx@deco.cx>
pedrofrxncx
pushed a commit
that referenced
this pull request
Aug 31, 2026
PR: #6689 feat(task-board): keep a card In Progress while its reviewer runs Bump type: minor - decocms (apps/api/package.json): 4.295.1 -> 4.296.0 - @decocms/native (apps/native/package.json): 4.295.1 -> 4.296.0 - @decocms/shared (packages/shared/package.json): 0.69.0 -> 0.70.0 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.
Summary
A card moved to In Review the moment its PR was opened, so it sat in the reviewers' lane for the whole time an agent was still working on it. In Review read as "waiting on a person" when nobody was waiting on anything.
Now:
Why this needed a column
The lane could not simply be changed. The review cycle — the boundary that decides which reviewer verdicts still count — was derived from the newest
status_changed → in_reviewactivity, which made the lane load-bearing: moving a card mid-review would reset the boundary and invalidate its own reviewer's verdict. That is the exact failure mode that stranded 13 prod cards holding an approval that could never merge.So the cycle stops riding on the lane:
review_cycle_started_at(migration 189) is the boundary, backfilled from the timeline so in-flight cycles survive the deploy.reviewCycleStart(activity, cycleStartedAt)reads the column, falling back to the old activity scan only for cards stamped before the migration. The option is a required field on every cycle reducer — a caller that forgot it would silently count a previous cycle's verdicts as current, so the compiler asks at each of the 11 call sites.inReviewPhase(item)replacesstatus === "in_review"in every automatic path, andlistItemsPendingReviewmirrors it (open cycle or the In Review lane — either alone leaves a real case unswept).openReviewCycleIfInProgressonly stamps a card with no cycle open, so the PR-open hook, the MCP tool hook and the thread-finish backstop are all idempotent. That predicate is the same guarantee the old conditional flip carried.Two regressions decoupling opens, closed explicitly
reactToFailedTaskRunskips a card with an open cycle — the reviewer has its own budget (spentAttemptsThisCycle), and the sweeper spends it.closesOwnReviewgated on the lane, so an author's own run could have marked its work Done while a reviewer was still reading it. It gates on the phase now.Testing
Real Postgres (embedded), not an in-memory fake:
→ in_reviewstamp, matching the old derivationbun test apps/api/src packages/shared apps/web/src→ no new failures vs. the branch point (183 pre-existing, unchanged)bun run check,bun run lint,bun run fmt,knipcleanNew/inverted tests: cycle idempotency + re-open-after-close + org scoping + the sweeper work list (real PG);
inReviewPhaseunit cases; and the tests that encoded the old lane behaviour are inverted rather than appended —advanceToDoneIfMerged,closesOwnReview,refuseIfMergePending, the PR-open reaction, and the implementer prompt.Screenshots
None — no visual change. The card renders in the In Progress column, which is existing lane rendering.
Summary by cubic
Keeps a card In Progress while its reviewer runs: linking a PR now opens a review cycle instead of moving the card to In Review, so the board only shows In Review once it's a person's turn.
The cycle boundary is now
review_cycle_started_at(migration 190) rather than the newest→ in_reviewtransition, backfilled from the timeline so in-flight cycles survive the deploy.inReviewPhasereplacesstatus === "in_review"in every automatic path, andopenReviewCycleIfInProgressis idempotent across the PR-open hook, MCP tool hook, and thread-finish backstop. A late PR link can no longer strand a card: a Super-Agent-owned card sitting In Review with no open cycle is pulled back to In Progress and stamped, while a card whose cycle is already open is left where it is.Regressions closed by the decoupling
closesOwnReviewgates on the phase, so an author's run can't mark its work Done while a reviewer is still reading it.starting-sandboxstatus is only announced on a real pod boot, not on warm pod resumes.Written for commit 13d3666. Summary will update on new commits.