Skip to content

fix(sync): fetch closed issues so closed⇒done enforcement actually runs#544

Merged
joryirving merged 4 commits into
mainfrom
fix/sync-includes-closed
Jul 2, 2026
Merged

fix(sync): fetch closed issues so closed⇒done enforcement actually runs#544
joryirving merged 4 commits into
mainfrom
fix/sync-includes-closed

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

  • All three syncIssuesForRepos callers (scheduled/manual/heartbeat) now fetch with { includeClosed: true }.

closedIssueStatusFix (#521) only runs on issues in the fetch set — and the fetch was state=open, so closed issues never passed through it. Seen in prod: closed #510 stuck at status/ready (claimable — the bridge actually claimed closed #507 this way). The gap was masked by the heartbeat cron's reconcileClosedIssues (which did its own includeClosed fetch); retiring the heartbeat in the scheduler cutover exposed it.

Verification

  • vitest run on sync/heartbeat/issue-sync → 80 passed (incl. new regression test asserting includeClosed: true); tsc + eslint clean.

Notes

  • First sync after deploy relabels all recently-stale closed issues (e.g. refactor(prisma): tighten the double as any on $transaction adapter #510) to status/done on GitHub — expected, self-limiting.
  • Fetch cost: state=all paginates full issue history per repo per sync. Fine at current repo sizes; if it ever matters, switch to a bounded recently-updated-closed fetch like fetchClosedPullRequests.

Jory Irving and others added 2 commits July 1, 2026 22:03
closedIssueStatusFix (#521) only applies to issues in the sync fetch
set, and every caller fetched state=open — so closed issues never
flowed through it and kept stale claimable labels (a closed issue
sat status/ready and got claimed by the bridge). The gap was masked
by the heartbeat cron, whose reconcileClosedIssues did its own
includeClosed fetch; retiring it (in-app scheduler cutover) exposed
this. All three sync callers now fetch state=all.
@joryirving joryirving enabled auto-merge (squash) July 2, 2026 04:05
@joryirving joryirving disabled auto-merge July 2, 2026 04:05
its-saffron[bot]

This comment was marked as outdated.

@joryirving joryirving enabled auto-merge (squash) July 2, 2026 18:49
@joryirving joryirving merged commit b36219c into main Jul 2, 2026
6 checks passed
@joryirving joryirving deleted the fix/sync-includes-closed branch July 2, 2026 18:52

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: public_route_changes)

Recommendation: Approve

This PR fixes a regression where closed issues were not being fetched during sync operations, causing the closed=>done enforcement (from PR 521) to silently not run. The fix consistently applies includeClosed: true across all three sync entry points.

Change-by-Change Findings

src/app/api/sync/route.ts

  • Added fetchAllStateIssues wrapper that passes { includeClosed: true } to fetchIssues
  • Replaced direct fetchIssues usage with fetchAllStateIssues in syncIssuesForRepos call
  • Comment explains the regression: heartbeat cron previously handled this via its own reconcileClosedIssues, but that code path was retired

src/app/api/sync/scheduled/route.ts

  • Identical fix: fetchAllStateIssues wrapper with includeClosed: true
  • Replaces direct fetchIssues in the scheduled sync path

src/lib/heartbeat.ts

  • Identical fix for the heartbeat sync path

src/app/api/sync/scheduled/route.test.ts

  • New regression test verifies fetchIssues is called with { includeClosed: true }

Required Checks

✅ verify route access controls are in place

The modified sync routes (/api/sync and /api/sync/scheduled) both retain their existing authorizeRequest guards:

if (!(await authorizeRequest(request)).authorized) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

No changes were made to authentication or authorization logic. The heartbeat.ts library is not a route; it is called by authorized callers and does not expose endpoints directly.

✅ check for unintended public endpoints

No new routes are introduced. The diff touches only:

  • Two existing API route handlers (path unchanged)
  • One internal library module (heartbeat.ts)

The PR is a pure logic fix (fetch closed issues so enforcement can run) with no routing surface changes.

Standards Compliance

  • Code Standards: No violations. Error handling uses instanceof Error patterns where applicable. No secrets committed.
  • API routes: Existing routes return appropriate 401 status codes.
  • No Prisma schema changes.
  • Tests pass, lint passes, typecheck passes, build passes (authoritative CI results).

Linked Issue Fit

This PR directly addresses the root cause described in the PR body: closed issues were stuck at status/ready (claimable) because the sync fetch was state=open. Issues PR 510 and PR 507 (mentioned in the body as examples of the problem) are unrelated refactoring work but serve as evidence that closed issues can incorrectly remain claimable.

The fix restores the intended behavior from PR 521 ("feat(sync): closed issues always move to status/done") by ensuring closed issues are included in the sync fetch set where the enforcement logic runs.

Evidence Provider Findings

No evidence providers configured.

Tool Harness Findings

No tool calls were issued; reviewing corpus directly.

Unknowns or Needs Verification

None. The fix is well-documented with inline comments explaining the regression root cause, includes a regression test, and all CI checks pass.

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.

refactor(prisma): tighten the double as any on $transaction adapter refactor(lane): remove dead lane-classification code + resolve /lane duplication

1 participant