fix(agents): tell the truth about inbox-lite status filter (BLO-18858) - #1117
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
allyblockcast
left a comment
There was a problem hiding this comment.
Approved the independently authored replacement. It carries #821 unchanged, including the reviewed lock-aware inbox/checkout behavior and full green CI from the exact code tree.
e6fcbcb to
17dcc3f
Compare
allyblockcast
left a comment
There was a problem hiding this comment.
Approved the rebased independent replacement on this exact head. The skills catalog was regenerated and validated; inbox contract tests pass 4/4.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 8d39f06
Critical Issues (1)
- [native-codex]
server/src/services/issues.ts:9232— Checkout can seize anin_reviewissue assigned to a human. The predicate permits every row whereassigneeAgentIdis null, even whenassigneeUserIdis set; the update then assigns the caller and clearsassigneeUserIdat lines 9213-9214. Thus the newin_reviewcheckout path silently removes a human review handoff. The added route test expecting a 409 for this case should expose the failure.- Require both assignee columns to be null before claiming an unassigned issue, or otherwise explicitly reject rows with
assigneeUserIdset; retain a regression test for the atomic update path.
- Require both assignee columns to be null before claiming an unassigned issue, or otherwise explicitly reject rows with
Important Issues (0)
Suggestions (0)
Strengths
- The status-filter documentation, MCP descriptions, and focused contract tests consistently distinguish routine inbox selection from named wakes.
- The pending execution-policy stage preserves
in_reviewthrough checkout, avoiding an accidental transition before its decision is recorded.
Recommended Action
- Fix the Critical issue before merge.
- Address Important issues this cycle.
- Consider Suggestions opportunistically.
There was a problem hiding this comment.
Ally — Consolidated PR Review
Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 82325d6
Prior Findings Dispositioned (1)
- prior:8d39f06 critical 1 — fixed —
server/src/services/issues.ts:9192— The checkout predicate now requires bothassigneeAgentIdandassigneeUserIdto be null before claiming an unassigned row, so the atomic update cannot remove a human review handoff.
Critical Issues (0)
Important Issues (1)
- [gstack/review]
server/src/__tests__/agent-inbox-lite-status-contract.test.ts:85— The newly added general-server suite is absent from the checked-in duration manifest. This drops coverage to 359/399 (89.97%), below the 90% assertion inscripts/__tests__/run-vitest-stable-shard.test.mjs:134;policyfails and skips all build, typecheck, and test lanes.- Regenerate and commit the Vitest duration manifest so this suite has a recorded duration and the required lanes can run.
Suggestions (0)
Strengths
- The inbox-lite contract is now consistent across MCP, OpenAPI, skills, and focused route coverage.
- The human-assignee guard fixes the prior atomic checkout regression without weakening named-review wake handling.
Recommended Action
- Fix Critical issues before merge.
- Address Important issues this cycle.
- Consider Suggestions opportunistically.
|
Heads-up on an overlap neither of us could have seen at the time: #1117 and #1116 were opened one minute apart (22:35Z and 22:36Z on 2026-08-06) and both target BLO-18858. I only found #1117 today, doing the dedup search that #1116's I am not touching this PR — flagging the collision so whoever sequences it does so deliberately. They contradict each other on one point
Both are defensible designs. They are not simultaneously true, and whichever lands second leaves the skills describing behaviour the service does not have — which is precisely the defect BLO-18858 was filed about. They also collide textually: both touch Current state of each, for whoever decides
One concrete thing that may unblock your
|
Rebase the approved inbox-lite, checkout, and stale-lock safeguards onto current master while preserving atomic checkout ownership and human assignment boundaries. Co-Authored-By: Paperclip <noreply@paperclip.ing>
82325d6 to
ad27950
Compare
|
Follow-up to my sequencing note above — I've now diffed the two properly, and the conclusion is simpler than "sequence them": #1117 supersedes #1116, and I'm closing #1116. Six of #1116's seven files are also in #1117, and #1117 carries the Step 3 Before this lands, one residual defect in #1117 that I'd fix here, because it's the exact practice half of BLO-18858's root cause. Step 4 still tells agents to hand-roll the lock check, and this PR now forbids thatThis PR adds to Critical Rules:
…and the matching Step 5 paragraph. But
That is the racy read your own new rule calls "a race, not a guardrail", and Two smaller things in that same block: the advice to "post a short comment noting the duplicate selection" has the losing run mutate an issue another run owns (bumps Suggested replacement for that Step 4 block**Selecting an issue is not claiming it — checkout is (Step 5).** Do not decide ownership by
comparing `$PAPERCLIP_RUN_ID` against the issue's `executionRunId` / `activeRun.id`. That read is
a race: the lock can be taken between your read and your first write, so a "nobody holds it"
answer can be stale by the time you act on it. Those fields are the lock's storage, not its API.
So: pick a candidate, then **let checkout be the ownership decision**. On `409`, you lost —
including to another live run of your own agent. Re-fetch the issue for diagnostics only and
**exit without mutating it**; do not post a "duplicate selection" comment, because a comment
bumps activity, can trigger another wake, and can enter the approval-comment state machine on an
issue another run owns. Read those fields when you want to *explain* a 409 in your own logs —
never to pre-authorize work.
Duplicate work is destructive rather than merely wasteful: under a shared worktree both runs edit
the same tree, and a routine `rm -rf node_modules` in one destroys the other's state mid-task.
Note that "no comments yet" is **not** evidence an issue is unworked — the holding run may be
minutes into its first pass and not have commented yet.That text is lifted from #1116 (where it was already reviewed clean), and it drops cleanly into this PR — it makes no claim about the Your call whether to take it here or let me open a follow-up once this lands; I'm not pushing to your branch either way. Flagging one more thing you may already know: this PR's checks are mid-run, and #1116 has been hitting an unrelated flake — |
…tage (BLO-22666) AC2: run-ownership was never asserted on an `in_review` row. ensureAgentCheckoutOwnership early-returns on `status !== "in_progress"`, and the approval-shaped-comment branch gates on identity only, so the SAME agent's second run could decide the stage run A was sitting on — the BLO-18858 duplicate-work defect, reintroduced through the review path that #1117 opened up. New `assertPendingReviewRunOwnership` is the `in_review` counterpart of assertCheckoutOwner, kept separate rather than widening that one: assertCheckoutOwner is keyed on `in_progress` in eight places and owns the stale-lock adoption ladder. The fence is deliberately narrow — live-locked AND `assigneeAgentId === actor` AND the stage is pending. By the time control reaches it the actor may legitimately be a mention-granted peer, a manager-chain actor, a recovery owner, a human, or a drifted `currentParticipant`; none hold the checkout and all must still approve. Widening past the assignee term would break the approval-by-comment path this issue exists to protect, which is a worse regression than the bug. AC3: a pending stage pinned to a `currentParticipant` whose id has drifted off `assigneeAgentId` could never take the lock, so its already-authorized stage decision raced whoever held it. The claim writes ONLY the lock columns — assignee, status and startedAt are untouched — so it cannot become a back door to general issue ownership, and the participant pin is re-asserted inside the UPDATE as a compare-and-swap.
…erage (BLO-24241) scripts/general-server-shard-durations.json was missing entries for 3 suites (99.25% coverage) and the >=90% coverage assertion that gated `policy` was a cliff: crossing it fails the whole job, which skips build, typecheck, e2e and every test lane over a single missing JSON entry (#1117 hit this at 89.97%). - Measure and backfill the 3 missing suites, restoring 100% coverage. - Replace the bare >=90% assertion with a much more generous HARD_FAIL_COVERAGE_FLOOR (0.75) that still warns (naming every missing suite and the one-line fix) on any drift without failing. - Add check-shard-manifest-freshness.{mjs,test.mjs}: a strict 100%-coverage assertion wired into pr.yml with continue-on-error, so drift stays visible as its own red X without cascading into `policy`. - Add measure-general-server-shard-durations.mjs to actually remeasure suites (used by both the new check's one-line fix and the scheduled workflow below). - Add .github/workflows/refresh-shard-manifest.yml: a weekly 4-shard measurement run that opens a PR with fresh durations, so the manifest no longer depends on a human noticing drift. - Extract shared suite-enumeration logic (walk/toRepoPath/route-authz exclusion) out of run-vitest-stable.mjs into run-vitest-stable-suites.mjs so the new diagnostics enumerate suites the same way the real runner does. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…erage (BLO-24241) scripts/general-server-shard-durations.json was missing entries for 3 suites (99.25% coverage) and the >=90% coverage assertion that gated `policy` was a cliff: crossing it fails the whole job, which skips build, typecheck, e2e and every test lane over a single missing JSON entry (#1117 hit this at 89.97%). - Measure and backfill the 3 missing suites, restoring 100% coverage. - Replace the bare >=90% assertion with a much more generous HARD_FAIL_COVERAGE_FLOOR (0.75) that still warns (naming every missing suite and the one-line fix) on any drift without failing. - Add check-shard-manifest-freshness.{mjs,test.mjs}: a strict 100%-coverage assertion wired into pr.yml with continue-on-error, so drift stays visible as its own red X without cascading into `policy`. - Add measure-general-server-shard-durations.mjs to actually remeasure suites (used by both the new check's one-line fix and the scheduled workflow below). - Add .github/workflows/refresh-shard-manifest.yml: a weekly 4-shard measurement run that opens a PR with fresh durations, so the manifest no longer depends on a human noticing drift. - Extract shared suite-enumeration logic (walk/toRepoPath/route-authz exclusion) out of run-vitest-stable.mjs into run-vitest-stable-suites.mjs so the new diagnostics enumerate suites the same way the real runner does. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Thinking Path
Linked Issues or Issue Description
Paperclip issue: https://paperclip.blockcast.net/BLO/issues/BLO-18858
Related incident: https://paperclip.blockcast.net/PCL/issues/PCL-2063
What happened?
paperclipInboxLitedescribedin_reviewassignments as part of its returned work, but the route correctly filters routine heartbeat selection totodo,in_progress, andblocked. An agent with onlyin_reviewassignments received[], interpreted that as a bad platform response, then used an unscoped issue-list sweep that had no checkout-lock awareness.Expected behavior
Agents should understand that an empty routine inbox is actionable only for unscoped heartbeat selection. Named task, comment, interaction, approval, monitor, continuation, and recovery wakes should process their named issue and claim it through checkout before work.
Steps to reproduce
in_reviewwork.paperclipInboxLitereturns[]even though the stale description impliedin_reviewwork would be listed.in_reviewwake and verify the agent contract directs a scoped read plus checkout instead of an unscoped sweep.Paperclip version or commit
Regression documented against the PR base/current master around
0b099719; the branch is now updated against current master.Deployment mode
Self-hosted server / local dev routes. This is not adapter-specific; it affects the Paperclip agent heartbeat contract and MCP tool description.
What Changed
paperclipInboxLitedocumentation in the skill, MCP tool description, and OpenAPI summary so it names the actual routine status set and distinguishes unscoped heartbeat selection from named issue wakes.issue-triageskill and regenerated the skills catalog manifest so triage can use a read-only, explicitlyin_review-scoped list before checkout.in_reviewcheckout rejection so assignedin_reviewissues can be atomically claimed intoin_progresswith checkout and execution run IDs.in_reviewcheckout, same-agent live-lock conflicts, and expected-status conflict behavior.Verification
pnpm exec vitest run server/src/__tests__/agent-inbox-lite-status-contract.test.ts server/src/__tests__/issues-service.test.ts server/src/__tests__/issue-stale-execution-lock-routes.test.ts --reporter=dotpassed non-DB assertions locally; embedded Postgres suites skipped on this host because embedded Postgres cannot initialize here.pnpm --filter @paperclipai/server typecheckpnpm --filter @paperclipai/skills-catalog exec vitest run src/shipped-catalog.test.ts --reporter=dotpnpm --filter @paperclipai/skills-catalog typecheckgit diff --checkRisks
Low to moderate risk. The behavioral change is intentionally narrow: unlocked assigned
in_reviewissues become checkoutable so the documented lock-aware claim path works. The main risk is any caller that relied on the old422 issue_in_review_not_checkoutableresponse, but the updated contract and tests make checkout the required concurrency boundary.Model Used
OpenAI Codex, GPT-5, with repository file access, GitHub CLI/API tool use, and local command execution.
Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue templateReplacement PR
kkrooso the required Ally review can be applied.b68092f08e3c644abb37ace819c4eeefb713336f.