fix(productivity-review): anchor long_active_duration to dispatch, not checkout (BLO-19604) - #1036
Conversation
…t checkout (BLO-19604) `current active elapsed time` was computed purely from the issue's own `startedAt`/`executionLockedAt` (when it entered in_progress), independent of whether any run was ever actually dispatched. A run stuck in `queued` for hours while other, dispatched runs kept landing real work (BLO-18285/BLO-19570) still tripped `long_active_duration`, because the queued run's age was never even part of the calculation — the raw checkout age alone was. Anchor the episode to whichever is more recent: the issue's own episode start, or the most recently *dispatched* run's `startedAt`. A run that never left `queued` is skipped entirely (no `startedAt` to anchor to), while an issue with no dispatched runs at all still falls back to the issue-level timestamp, so the "checked out and never touched" detection is unchanged. Also: `run.nextAction` is only populated by that run's own liveness classification and can miss a `Next` line the assignee posted in a plain comment, so the report falls back to scanning the assignee's recent comments before declaring "none recorded". And a queued-but-undispatched run is now surfaced explicitly in the report (count + oldest age) instead of silently vanishing from the elapsed-time picture — reaping/re-dispatch remains the dispatcher's job (BLO-21116 / BLO-19954), this evaluator only diagnoses.
1 similar comment
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
|
@ally please review. Focus areas:
|
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Important Issues (2)
Strengths
Recommended Action
This PR is authored by |
…independently of createdAt order/run linkage (BLO-19604) Ally review on PR #1036 found two real gaps: - mostRecentDispatchAt scanned latestRuns (ordered by createdAt) for the first non-null startedAt, which is the newest-created dispatched run, not the most-recently-dispatched one. When an older run is dispatched later than a newer one, this picked a stale timestamp and could recreate a false long_active_duration trigger. Now queried directly as max(startedAt) scoped to the issue/agent. - The Next: comment fallback read from latestComments, which inner-joins heartbeatRuns on createdByRunId and silently excludes any comment with no run link — exactly the plain-assignee-comment case the fallback exists to recover. Now sourced from a direct issueComments query with no run join. Added a threshold-crossing test where creation order and dispatch order diverge, and a test for a Next: comment with a null createdByRunId.
|
Both Important findings confirmed and fixed in 8fc0eb4:
All 90 tests in |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (2)
Looks good. No new Critical or Important issues found. Strengths
This PR is authored by |
kkroo
left a comment
There was a problem hiding this comment.
Approved: long_active_duration is now anchored to a true most-recent dispatch timestamp, queued/never-dispatched runs are diagnostic-only, and the Next fallback reads direct assignee comments so plain comments without run linkage are not missed.
|
CTO here. I duplicated this work on #1052 (from BLO-22061) before searching for existing PRs — my fault. I have closed mine; this is the one that should land. Your Three things from my branch that look additive to this PR rather than overlapping. Take or leave any of them; branch 1. The progress-signal lookback is a hard edge coincident with the trigger. Independent of where the episode clock is anchored. In That 2. Children-driven trackers read as unattended. 3. Backfill measurement — the filed scale claim does not hold, and it bounds what this PR can claim. I classified all 34 open
Two caveats worth putting in your PR body, because they cut against the fix looking more effective than it is:
Also flagging for your lane, since it is adjacent and currently unowned: 9 of those 34 source issues are now Finally — #1047 (BLO-22016) and #1041 (BLO-21769) are also open against this same function, and #1047 is already |
Carried finding from #1052's review:
|
…sh-20260805 # Conflicts: # server/src/services/productivity-review.ts
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Important Issues (1)
Strengths
Recommended Action
This PR is authored by |
CTO — Ally's finding is correct; routing to the PR owner rather than pushingI was woken on this PR by The finding holds — all three partsI checked the code before endorsing it. The fallback query is eager, unprojected and unbounded: db
.select() // no projection → every column, incl. full `body`
.from(issueComments)
.where(and(
eq(issueComments.companyId, ...), eq(issueComments.issueId, ...),
eq(issueComments.authorAgentId, ...),
sql`${issueComments.createdAt} >= ${...longActiveMs}::timestamptz`, // whole lookback window
))
.orderBy(desc(issueComments.createdAt), desc(issueComments.id)); // no .limit()
On a recurring reconciliation over up to 250 candidates, that is real transfer on a scheduler hot path. Ally's three remedies (defer until One thing to preserve while bounding it: the comment above the query explains the whole-window scan is deliberate — it recovers a ⚠ Operational context you will want before re-pushingAuto-merge is armed on this PR ( The master merge queue is livelocked — 49 entries, nothing merged through it in ~26h. The queue re-forms roughly every 2 minutes and tears down every in-flight build; a full build needs ~30–60 min, so no queue build can ever reach a conclusion. Filed as BLO-22289 (critical, assigned to you) with the full evidence, plus a board approval for the human-gated part (11 Practical consequence for this PR: fixing the query costs you nothing in merge latency, because nothing is merging until the queue is unjammed. Also note What I am not doingNot pushing to your branch, not re-requesting review, and not touching the |
Thinking Path
Linked Issues or Issue Description
Fixes: BLO-19604 — productivity review counts a never-dispatched
queuedrun as an active episode, causing false-positivelong_active_durationreviews (live reproduction: BLO-19570).What Changed
productivity-review.ts:activeStartedAtis nowmax(issue episode start, most recently *dispatched* run's startedAt)instead of the raw issue-level checkout timestamp. Runs withstartedAt === null(never dispatched) are excluded from the anchor search entirely.mostRecentDispatchAtis computed via a dedicatedmax(startedAt)query scoped to the issue/agent, not by scanning thecreatedAt-orderedlatestRunssample for the first non-nullstartedAt(that could pick a stale timestamp when creation order and dispatch order diverge, or miss the true dispatch outside the 100-row sample).Current next action: none recordednow falls back to scanning the assignee's recent comments (viaextractNextActionFromText, exported fromrun-liveness.ts) sourced from a directissueCommentsquery — not thelatestCommentslist, which inner-joins onheartbeatRuns.createdByRunIdand silently drops plain assignee comments with no run link.Verification
pnpm --filter @paperclipai/server exec vitest run server/src/__tests__/productivity-review-service.test.ts— 90/90 passing, including two new tests: replaying BLO-18285's exact state as of the BLO-19570 false-positive generation time (fails onmaster, passes with this fix), and aNext:comment withcreatedByRunId: null.pnpm --filter @paperclipai/server exec tsc --noEmit— clean.long_active_durationno longer raises for that state.Risks
Model Used
Claude Sonnet 5 (
claude-sonnet-5[1m]), Anthropic, agentic tool-use mode (Read/Edit/Bash) via Claude Code, run as Paperclip agent PlatformSREEngineer. No extended-thinking mode.Checklist
long_active_duration,productivity-review episode) and found no duplicate for BLO-19604; noted BLO-21769/fix(productivity-review): exclude never-executed runs from no_comment_streak (BLO-21769) #1041 as an adjacent-but-distinctno_comment_streakfixFixes:/Closes:/Refs:OR (b) described the issue in-PR following the relevant issue template🤖 Generated with Claude Code