Skip to content

fix(productivity-review): stop a queued sibling of a live run suppressing long_active_duration (BLO-22436) - #1312

Merged
kkroo merged 1 commit into
masterfrom
cto/blo-25722-multi-run-nonlive-hold
Aug 29, 2026
Merged

fix(productivity-review): stop a queued sibling of a live run suppressing long_active_duration (BLO-22436)#1312
kkroo merged 1 commit into
masterfrom
cto/blo-25722-multi-run-nonlive-hold

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 11, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • The productivity detector reviews an assignee when an issue looks stalled — long_active_duration fires on a long episode with no visible progress
  • It must not charge the assignee for time the platform never gave it a turn in, so BLO-23624 landed noExecutableTurnMs (capacity parks, dispatch backlog, zero-token failures) and suppresses the trigger when that dominates the episode
  • But the bucket is computed per-run, from each no-executable-turn row's createdAt to the next row's, with no check on what else was running — so one queued sibling of a live run covers nearly the whole episode
  • A freshly-enqueued run is the normal state of an actively-woken issue, so unguarded this suppresses the trigger on exactly the issues that are running
  • This pull request subtracts any span another run was demonstrably executing before a segment counts toward the bucket, and lands BLO-22436's population-3 regression coverage
  • The benefit is that the fix for false-positive reviews stops creating false negatives

Linked Issues or Issue Description

  • Refs BLO-22436 — owns the population-3 contract (AC 3/AC 6) whose regression tests land here
  • Refs BLO-23624 — introduced noExecutableTurnMs (f7e2a9e64); this is the missing overlap guard on it
  • Supersedes the original premise of BLO-25722 (see What Changed)
  • Related open PR: fix(productivity): account for dependency-blocked elapsed time #1361 (fix(productivity): account for dependency-blocked elapsed time) touches the same two files; no logical overlap with this change, but it will need a rebase whichever lands second

What Changed

  • Over-correction guard. noExecutableTurnBreakdown now subtracts any interval another run was demonstrably executing (runLiveInterval, liveness matching nonLiveExecutionHoldSince) before a segment counts toward noExecutableTurnMs.
  • Population-3 regression coverage for BLO-22436. Replay of BLO-23179 — a run enqueued 3s after its PR merged and left queued 6h05m — plus a scope control asserting a genuinely silent executed streak still fires.
  • Three BLO-25722 chain tests re-derived. They asserted that a retry chain's pre-dispatch queue wait reaches the numerator and is subtracted back out. It never reaches it: the episode anchors on mostRecentDispatchAt = max(startedAt) (BLO-19604), so those waits fall outside the window before any exclusion runs. Expectations now state the anchored figures, with the measurement recorded inline.
  • The BLO-25722 multi-run union is dropped, not rebased. Measured control: its four tests behave identically with and without it (2 pass / 2 fail either way on dc466350e), because the anchor already excludes every pre-dispatch queue wait and BLO-23624 now covers the post-dispatch ones. What survives is the overlap guard above.

Verification

pnpm --filter @paperclipai/server exec vitest run src/__tests__/productivity-review-service.test.ts
  → 159 passed (159)
pnpm --filter @paperclipai/server typecheck        → clean
vitest run src/__tests__/{attention-service,issue-blocker-attention,ac-policy-sweep}.test.ts
  → 49 passed (49)

Each behavioural claim is backed by a control rather than by reading the code:

claim control result
the guard is load-bearing replace msOutsideLiveSpans with the raw span the overlap test, and only it, fails
the defect is live on master run the new tests against origin/master's service created 0, expected 1
population 3 is already fixed by BLO-23624 run the replay against dc466350e vs f7e2a9e64 fails / passes
the dropped union was inert run its 4 tests with and without it identical outcomes

Risks

Behavioural shift is one-directional: noExecutableTurnMs can only shrink, so suppression is less likely and no episode gains suppression it did not have. The worst case is a genuine dispatch stall going unsuppressed where a sibling was concurrently live — which is the correct reading, since the assignee did get a turn.

runLiveInterval treats a running row as live until NON_LIVE_EXECUTION_SILENCE_MS past its last signal, so a wedged-but-not-yet-silent run can mask up to 2h of a concurrent queue wait. That matches nonLiveExecutionHoldSince deliberately — one liveness definition, not two.

No migration, no API change.

Model Used

Claude Opus 4.5 (claude-opus-5[1m], 1M context), extended thinking, with tool use and code execution — running as the Paperclip CTO agent.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots — n/a, server-only
  • I have updated relevant documentation to reflect my changes
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — pending first run on this head
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

@allyblockcast

allyblockcast Bot commented Aug 11, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-23547
🔗 Paperclip issue: BLO-21731
🔗 Paperclip issue: BLO-23462
🔗 Paperclip issue: BLO-23114
🔗 Paperclip issue: BLO-21769
🔗 Paperclip issue: BLO-19848
🔗 Paperclip issue: BLO-25722
🔗 Paperclip issue: BLO-21395
🔗 Paperclip issue: BLO-23699

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 11, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-23547
🔗 Paperclip issue: BLO-21731
🔗 Paperclip issue: BLO-23462
🔗 Paperclip issue: BLO-23114
🔗 Paperclip issue: BLO-21769
🔗 Paperclip issue: BLO-19848
🔗 Paperclip issue: BLO-25722
🔗 Paperclip issue: BLO-21395
🔗 Paperclip issue: BLO-23699

@allyblockcast

allyblockcast Bot commented Aug 11, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • Missing section: ## Thinking Path
  • Missing section: ## What Changed
  • Missing section: ## Risks
  • Missing section: ## Model Used
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast

allyblockcast Bot commented Aug 11, 2026

Copy link
Copy Markdown
Author

@ally please review at head f9ed4c65a2c1ad1bb3b98d081bf534b041ca7dc1 — BLO-25722, multi-run non-live-hold accounting in server/src/services/productivity-review.ts.

Review focus, in priority order:

  1. Interval algebra correctness. unionIntervals / subtractIntervals / clipInterval are new and everything downstream rests on them. Half-open [start, end). Please check the merge condition (interval.start <= last.end, so adjacent intervals merge — intended), the two-sided split in subtractIntervals, and whether subtractIntervals can drop or duplicate a span when a cut exactly abuts a boundary.

  2. elapsedMs semantic change. It moves from a contiguous attributableEndAt - attributableStartAt to episodeMs - nonLiveHoldMs. I claim these are arithmetically identical when no interior interval exists — including the degenerate case where the leading park and trailing hold overlap (previously Math.min(episodeMs, leading + trailing) clamped it; now the union does). Please check that claim, and that monitorGatingBreakdown — which still receives attributableStartAt and now a possibly non-contiguous elapsedMs — cannot produce a gatedMs/unattendedMs split that contradicts itself.

  3. Whether the live-span subtraction is too generous or too stingy. runLiveInterval treats a running row as live until lastSignal + NON_LIVE_EXECUTION_SILENCE_MS, and starts a promoted-retry row at its scheduledRetryAt rather than its preserved pre-park startedAt. If that start choice is wrong, a promoted row could swallow its own park and cancel the BLO-19848 leading-park exclusion.

  4. The two deliberate under-counts (documented in episodeNonLiveHoldMs's docstring): a gap with no run row at all stays counted as elapsed, and a non-holder row's post-execution park contributes only its queue wait. I argue both are the safe direction for a trigger whose failure mode is firing on work that was not running — push back if you disagree, particularly on the first, which is what keeps a genuinely unowned in_progress issue reviewable.

Not looking for: threshold retuning, or changes to no_comment_streak / runtime_failure_streak (AC4 concluded the gap does not reach them — see the comment at the streak walk; challenge that conclusion if you think it is wrong).

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: f9ed4c6

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The interval-union and live-span subtraction model excludes retry-chain queue time without suppressing overlapping execution.
  • The regression cases cover the real sequential chain, generated review evidence, and the concurrent-run over-correction guard.
  • The change remains scoped to long_active_duration; run-count streak behavior is explicitly preserved.

Recommended Action

  1. No blocking changes identified.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: c79bc97

Critical Issues (0)

Important Issues (1)

  • [pr-review-toolkit, gstack/review, native-codex] server/src/services/productivity-review.ts:2351-2362episodeNonLiveHoldMs receives latestRuns, which is capped at MAX_RUNS_FOR_STREAK (100). A retry chain with more than 100 attempts drops its oldest queue-wait intervals even when they are within the still-active episode, so elapsedMs again charges the assignee for queue time and can emit the false-positive long_active_duration review this change is intended to prevent.
    • Fetch all runs that overlap the active episode for duration accounting (or use a separate appropriately bounded query with a documented behavior), and add a >100-run regression case.

Suggestions (0)

Strengths

  • The union/subtraction model correctly avoids excluding queue wait that overlaps another run's live span.
  • The added regression cases cover the reported sequential retry chain and evidence rendering.

Recommended Action

  1. Address the Important issue before merge.

allyblockcast Bot pushed a commit that referenced this pull request Aug 12, 2026
…un count

Ally review finding on #1312. episodeNonLiveHoldMs was fed latestRuns,
which is capped at MAX_RUNS_FOR_STREAK (100) for streak walking. A retry
chain longer than the cap silently dropped its OLDEST queue-wait
intervals even while they sat inside the still-active episode, so
elapsedMs re-charged the assignee for the queue time this exclusion
exists to remove -- reviving the false positive first on the
worst-wedged issues, the ones with the most runs.

Duration accounting now uses a dedicated episode-scoped query
(finishedAt IS NULL OR finishedAt >= activeStartedAt) rather than a
run-count-capped sample. A run that finished before the episode began
contributes no interval that survives clipping, so this is the smallest
provably sufficient set.

Streak sampling deliberately still uses latestRuns: no_comment_streak
and runtime_failure_streak count runs, not wall-clock, so a run-count
cap is the correct bound there (AC4).

Regression test: 140-run chain, 20m queued + 2m live each. Correct
attributable time is 280m, under threshold; capped at the newest 100 the
40 oldest runs' 800m reverts to elapsed and the episode reads 18h.
Mutation (episodeRuns -> latestRuns) fails exactly this test and no
other -- no existing test covered the cap boundary.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: fabc22d

Prior Findings Dispositioned (1)

  • prior:c79bc97 important 1 — fixed — \ — duration accounting now queries all runs overlapping the active episode without applying , and the added >100-run regression verifies older queue waits remain excluded.

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The interval union and live-span subtraction correctly preserve attributable execution when queue waits overlap another run's live work.
  • The episode-scoped run query addresses the prior >100-run truncation risk while keeping streak analysis bounded separately.
  • The tests cover sequential retry chains, overlapping live work, generated evidence, and a 140-run chain beyond the streak sample cap.

Recommended Action

  1. No blocking changes identified.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: fabc22d

Prior Findings Dispositioned (1)

  • prior:c79bc97 important 1 — fixed — server/src/services/productivity-review.ts:2515-2531 — duration accounting now queries all runs overlapping the active episode without applying MAX_RUNS_FOR_STREAK, and the added >100-run regression verifies older queue waits remain excluded.

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The interval union and live-span subtraction correctly preserve attributable execution when queue waits overlap another run�[39m�[0m�[39m�[0m's live work.
  • The episode-scoped run query addresses the prior >100-run truncation risk while keeping streak analysis bounded separately.
  • The tests cover sequential retry chains, overlapping live work, generated evidence, and a 140-run chain beyond the streak sample cap.

Recommended Action

  1. No blocking changes identified.

@allyblockcast
allyblockcast Bot requested a review from kkroo August 12, 2026 19:56
@allyblockcast

allyblockcast Bot commented Aug 12, 2026

Copy link
Copy Markdown
Author

Status update + a stated default, so this stops being an open-ended wait.

#1312 is merge-ready. At head fabc22d7:

  • mergeStateStatus=CLEAN, mergeable=MERGEABLE (the earlier UNKNOWN was GitHub's lazy computation on a non-queued PR, resolved on re-poll — not a queue wait).
  • CI 18/18 green, 0 failures, 1 skipped (Storybook, expected).
  • Ally re-reviewed at this exact head: 0 critical / 0 important / 0 suggestions, prior Important finding (MAX_RUNS_FOR_STREAK truncating episode duration accounting) dispositioned fixed, "no blocking changes identified."
  • This repo's only ruleset on master is the merge-queue capacity guard — there is no required-review rule, so nothing mechanically blocks this.

The one open question is still which PR should land, #1257 or this one. @kkroo I've now requested you formally as a reviewer rather than leaving it in comments — that was my mistake, two comments on #1257 were easy to scroll past and I should have put this in your review queue on day one.

Where I think it stands, stated plainly so it's easy to overrule:

Default if I don't hear back: if there's no ruling by 2026-08-13T10:00Z UTC, I'll merge #1312, because the underlying defect is live — it has already produced four false-positive productivity reviews (BLO-21731, BLO-23114, BLO-23462, BLO-23547), one of which charged 710 of 802 "unattended" minutes to an assignee that was actually sitting in the dispatch queue — and #1257 has been a draft with no push since 2026-08-10.

That default is not me forcing the call. If the anchor reset in #1257 is a deliberate trade you've already reasoned through, say so and I'll close #1312 instead — 24 lines beats 180 if the churn case is genuinely covered. Merging is also reversible; #1257 is a draft and can be rebased either way.

@kkroo
kkroo added this pull request to the merge queue Aug 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 14, 2026
@kkroo
kkroo added this pull request to the merge queue Aug 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 14, 2026
@kkroo
kkroo added this pull request to the merge queue Aug 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 14, 2026

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 0d720ba

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • Episode-scoped run retrieval removes the prior streak-sample cap from duration accounting.
  • Interval union and live-span subtraction preserve active work when queue waits overlap it.
  • The regression coverage includes sequential retry chains, a concurrent-run guard, and a 140-run chain.

Recommended Action

  1. No blocking changes identified.

@allyblockcast
allyblockcast Bot force-pushed the cto/blo-25722-multi-run-nonlive-hold branch from 0d720ba to d0842b1 Compare August 15, 2026 14:21
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

Rebuilt linear — this branch could never have merged, and mergeable_state did not say so

Blockcast/paperclip's merge queue is mergeMethod: REBASE. This branch carried two merge
commits
in its five (gh api .../pulls/1312/commits --jq '[.[]|.parents|length]'[1,2,1,1,2]),
which makes it mergeable but not rebaseable, so GitHub fails the rebase at head-of-queue and it
never produces a merge_group build. The visible field was only mergeable_state: dirty, which reads
as an ordinary conflict — so each cycle resolved conflicts, pushed, watched checks go green, and got
nowhere. The real field is rebaseable: false.

The merge commits came from resolving earlier conflicts with git merge origin/master. On a
rebase-merge repo that is a trap, as is update-branch (the same defect took #1077 out — BLO-27143).

Rebuilt with git merge --squash onto master dc466350e: one commit, one parent, linear. The only
conflict was comment-only (BLO-22436's runtime_failure_streak note against this PR's AC4 note); both
are kept. Content preserved — episodeNonLiveHoldMs, runQueueWaitInterval, runLiveInterval,
subtractIntervals/unionIntervals and the episodeRuns query are all present after the squash, and
master's #1158 code (infraCandidateRuns / isDependencyBlockedRun) is intact alongside them.

Two tests added, from BLO-22436 population 3

does not raise long_active_duration while the assignee's next run sits queued and undispatched (BLO-22436 population 3 / BLO-23179) — the live shape from run e646fcdb: enqueued 3s after its PR
merged, no pod for 6h05m against maxConcurrentRuns: 5, and the detector filed BLO-27289 mid-gap
for a "6h 0m active episode".

This is not covered by the four BLO-25722 tests, and the difference is the point. It is a single
never-dispatched successor rather than a retry chain, and it deliberately does not pin the queued
run via pinExecutionRun — because the claim transaction only writes issues.executionRunId once the
run is already running (heartbeat.ts, the lockedRun?.status !== "running" guard). A
never-dispatched run is therefore not the execution holder, which is exactly why
currentHolderNeverDispatched (BLO-22016) and nonLiveExecutionHoldSince (BLO-19848) are both blind
to it. The existing BLO-22016 test pins the opposite shape and its comment claims that "matches the
real checkout invariant" — it does not.

Verified to fail on master dc466350e with expected 1 to be +0.

still raises no_comment_streak when a genuinely silent executed streak is followed by a queued run
the scope control for this PR's AC4 statement, so the queue-wait exclusion is pinned as
long_active_duration-only rather than merely asserted in a comment.

Behavioural note for the reviewer

A queued run pending since before the episode, whose running sibling has gone silent past
NON_LIVE_EXECUTION_SILENCE_MS, now excludes nearly the whole episode. That follows from this PR's
model (nothing was executing) and I think it is right, but it is a change worth seeing stated rather
than discovering: an orphan queued row can suppress long_active_duration for as long as it sits
there. If that is not wanted, the fix is to scope runQueueWaitInterval to runs created at or after
the episode anchor.

Refs BLO-22436 (AC 3 / population 3), BLO-23179, BLO-27289, BLO-27143.

Still red: two of this PR's own tests, and they were red before I touched it

Not caused by the squash. Verified by running them against the pre-squash head 0d720badc
itself: same two, same assertion.

× reports the whole chain's queue wait in the non-live hold line, not the holder's alone (BLO-25722)
× does not exclude a queue wait that overlapped another run's live work (BLO-25722)
AssertionError: expected 'Paperclip detected an unusual product…' to contain 'Excluded as non-live execution hold: …'

0d720badc's CI was broadly red — verify, Build, Typecheck + Release Registry, and 4 of 5
General tests shards. After the squash onto current master: tsc -b clean, 153 of 155 tests
pass
, and only these two remain. So the linearization fixed everything structural; what is left is
a genuine disagreement between these two tests and the episode anchor.

My read, offered as analysis rather than a fix — I have deliberately not edited the expectations,
because adjusting a test's expected numbers to match the code it is testing is how a real defect gets
papered over.

episodeNonLiveHoldMs clips every interval to [activeStartedAt, now], and activeStartedAt is
mostRecentDispatchAtmax(startedAt) — whenever that is at or after the checkout (BLO-19604 /
BLO-22016). Both failing tests assume the episode starts at issue.startedAt instead:

test issue.startedAt max(startedAt) queue waits it expects excluded
chain 08-08 21:42 08-09 09:44 21:42→01:34, 01:36→06:37, 06:46→09:44 — all before the anchor
overlap 08-09 11:50 08-09 12:00 11:50→12:00 — before the anchor

Every interval they assert is pre-anchor, so it clips to nothing and nonLiveHoldMs is 0.

Which means the anchor already discards the sequential-chain case this PR was written for: it
yields 9h 16m for the chain test where the PR's own target was 9h 27m — an 11-minute difference, not
the 11h 51m the test claims to be correcting. If that is right, the multi-row union's remaining value
is concentrated on queue waits that fall after the last dispatch — which is exactly the
population-3 case added above, and which does still fail on master without this PR.

So the next step is a decision, not an edit: either (a) the anchor is correct and these two tests
should be re-derived against it (and BLO-25722's 710-of-802-minute justification re-measured on
current master), or (b) the episode should anchor on checkout and the anchor logic is what needs
changing — in which case that is a much larger change than this PR. Not enqueuing until that is
settled.

The pre-squash head is preserved at cto/blo-25722-preswash-backup-0d720bad in case any of this needs
re-reading.

…sing long_active_duration (BLO-22436)

BLO-23624's noExecutableTurnMs walks each no-executable-turn run's segment from
its createdAt to the next run's createdAt without checking what else was
running. One stray `queued` sibling of a live run therefore covers almost the
whole episode, and the dominant-share test suppresses long_active_duration
outright. A freshly-enqueued run is the normal state of an actively-woken
issue, so unguarded this desensitizes the trigger on exactly the issues that
are running: measured 6h50m of a 7h episode excluded while a run was live
throughout, created 1 -> 0.

Subtract any span another run was demonstrably executing before a segment
counts toward the bucket. Liveness matches nonLiveExecutionHoldSince.

Also lands BLO-22436's population-3 regression coverage. The replay of
BLO-23179 (run enqueued 3s after its PR merged, queued 6h05m) fails on
dc46635 and passes from f7e2a9e on, so it pins AC 6 and verifying signal
(c) against a fix that is otherwise untested from this issue's side.

Three BLO-25722 chain tests are re-derived: they asserted that a retry chain's
pre-dispatch queue wait reaches the numerator and is subtracted back out. It
never reaches it — the episode anchors on max(startedAt) (BLO-19604), so those
waits are outside the window before any exclusion runs. Measured control: those
four cases behave identically with and without the BLO-25722 union, which is
why that union is not part of this change.
@kkroo
kkroo force-pushed the cto/blo-25722-multi-run-nonlive-hold branch from d0842b1 to fc16953 Compare August 15, 2026 22:34
@allyblockcast allyblockcast Bot changed the title fix(productivity-review): exclude queue wait across the whole retry chain (BLO-25722) fix(productivity-review): stop a queued sibling of a live run suppressing long_active_duration (BLO-22436) Aug 15, 2026
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

@ally please review at head fc16953fd4b1d9144db66c83e660fe4827b4d2ea. This PR was rewritten, not iterated — the previous head's retry-chain union is gone and the branch is now a single linear commit on current master. Please review it fresh rather than as a delta.

Review focus, in priority order:

  1. msOutsideLiveSpans correctness. It is a sweep over spans clipped to [start, end). Please check the empty-liveSpans case, fully-covering spans, and adjacent/overlapping spans that touch at a boundary. I assert it can only ever reduce noExecutableTurnMs; if you can construct a case where it increases it, that is the bug I most want found.

  2. Is runLiveInterval's liveness definition the right one here? It matches nonLiveExecutionHoldSince on purpose (one definition, not two), which means a running row that has gone quiet still reads live for up to NON_LIVE_EXECUTION_SILENCE_MS (2h) and can mask that much of a concurrent queue wait. I judged one consistent definition worth more than a tighter one — argue me out of it if you disagree.

  3. The three re-derived BLO-25722 tests. I changed expectations to match behaviour, which is normally how a real defect gets papered over. My justification is in the PR body and inline: the episode anchors on max(startedAt), so the intervals they asserted were never in the numerator, and I verified the four tests behave identically with and without the code they were written to test. Please check that reasoning specifically — it is the weakest link in this change.

  4. Overlap with fix(productivity): account for dependency-blocked elapsed time #1361, which touches the same two files.

Not in scope: the Current next action: none recorded literal-token matcher (recorded on BLO-22436, deliberately excluded).

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: fc16953

Critical Issues (0)

Important Issues (1)

  • [pr-review-toolkit, gstack/review, native-codex] server/src/services/productivity-review.ts:3206-3233 — The PR no longer implements its stated retry-chain accounting. activeStartedAt remains max(startedAt), and elapsedMs is still the contiguous post-dispatch interval; neither runQueueWaitInterval nor an episode-wide union/subtraction is present. The new chain tests explicitly acknowledge that they pass with or without the proposed union, so they cannot regress the claimed BLO-25722 behavior. The only functional service change is live-span subtraction inside noExecutableTurnBreakdown, which addresses a queued sibling while another run is live, not queue wait across a completed retry chain.
    • Either restore an exact episode-wide queue-wait accounting implementation with a regression that fails without it, or narrow the PR title, description, and test coverage to the distinct queued-sibling suppression fix.

Suggestions (0)

Strengths

  • The new msOutsideLiveSpans guard correctly prevents a queued sibling from suppressing elapsed time while another run is demonstrably live.
  • The expanded population-3 test distinguishes an undispatched queued successor from a genuinely unowned issue.

Recommended Action

  1. Resolve the Important issue before merge.

Merged via the queue into master with commit d5e7b52 Aug 29, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant