Description
The async ISSUE_CLOSURE fetch job can write issues.solved_by_pr onto an issue that has already been reopened (state = OPEN). IssueHandler clears solved_by_pr synchronously on reopen, but a slow in-flight closure job performs a slow GraphQL fetch and then unconditionally updates solved_by_pr without re-checking issue state or guarding the write with WHERE state = 'CLOSED'.
This corrupts issue-discovery scoring inputs: an OPEN issue can carry a solver PR link that should only exist on a closed issue.
Steps to Reproduce
-
Close an issue that is solved by a merged PR. The issues.closed webhook upserts the row and enqueues ISSUE_CLOSURE with job id closure-{repoFullName}-{issueNumber}.
-
Before the job finishes its GraphQL timeline fetch (fetchIssueClosingPr), reopen the issue on GitHub.
-
The issues.reopened webhook runs first: upserts state = OPEN, solved_by_pr = null.
-
The already-running ISSUE_CLOSURE job still holds a stale in-memory read from step 1 (state = CLOSED) and continues into fetchIssueClosingPr.
-
When the job completes, it executes:
await this.issueRepo.update({ repoFullName, issueNumber }, { solvedByPr });
with no state guard.
-
Query issues for that issue number — state = OPEN but solved_by_pr is populated again.
Expected Behavior
solved_by_pr should only be set while the issue remains closed. If the issue was reopened before the async job completes, the job should no-op (or re-read state and skip the write).
Actual Behavior
A reopened issue can be left with a non-null solved_by_pr, contradicting the synchronous reopen handling in IssueHandler and downstream assumptions that OPEN issues have no solver attribution.
Environment
- OS: Linux
- Runtime/Node version: Node 20 (NestJS + BullMQ worker)
- Browser (if applicable): N/A
Additional Context
Affected code
packages/das/src/queue/fetch.processor.ts — handleIssueClosure() reads issue state once at lines 118–122, then writes { solvedByPr } unconditionally at line 139.
packages/das/src/webhook/handlers/issue.handler.ts — reopen path correctly sets data.solvedByPr = null at lines 44–46, but cannot prevent a later async overwrite.
Why this is distinct from open items
Suggested fix direction
- Re-read
issues.state (or use UPDATE … WHERE state = 'CLOSED' RETURNING …) immediately before writing solved_by_pr.
- Optionally skip
fetchIssueClosingPr entirely if the issue is no longer closed after the GraphQL call returns.
Impact
solved_by_pr feeds issue-discovery scoring. An OPEN issue with a solver link can incorrectly count as solved or skew credibility ratios until a later webhook/backfill corrects it.
Description
The async
ISSUE_CLOSUREfetch job can writeissues.solved_by_pronto an issue that has already been reopened (state = OPEN).IssueHandlerclearssolved_by_prsynchronously on reopen, but a slow in-flight closure job performs a slow GraphQL fetch and then unconditionally updatessolved_by_prwithout re-checking issue state or guarding the write withWHERE state = 'CLOSED'.This corrupts issue-discovery scoring inputs: an OPEN issue can carry a solver PR link that should only exist on a closed issue.
Steps to Reproduce
Close an issue that is solved by a merged PR. The
issues.closedwebhook upserts the row and enqueuesISSUE_CLOSUREwith job idclosure-{repoFullName}-{issueNumber}.Before the job finishes its GraphQL timeline fetch (
fetchIssueClosingPr), reopen the issue on GitHub.The
issues.reopenedwebhook runs first: upsertsstate = OPEN,solved_by_pr = null.The already-running
ISSUE_CLOSUREjob still holds a stale in-memory read from step 1 (state = CLOSED) and continues intofetchIssueClosingPr.When the job completes, it executes:
with no state guard.
Query
issuesfor that issue number —state = OPENbutsolved_by_pris populated again.Expected Behavior
solved_by_prshould only be set while the issue remains closed. If the issue was reopened before the async job completes, the job should no-op (or re-read state and skip the write).Actual Behavior
A reopened issue can be left with a non-null
solved_by_pr, contradicting the synchronous reopen handling inIssueHandlerand downstream assumptions that OPEN issues have no solver attribution.Environment
Additional Context
Affected code
packages/das/src/queue/fetch.processor.ts—handleIssueClosure()reads issue state once at lines 118–122, then writes{ solvedByPr }unconditionally at line 139.packages/das/src/webhook/handlers/issue.handler.ts— reopen path correctly setsdata.solvedByPr = nullat lines 44–46, but cannot prevent a later async overwrite.Why this is distinct from open items
issue.closedAtattribution for re-closed issues; this is a concurrency race between reopen and an in-flight closure job.solved_by_pron reopen via webhook; this bug re-introduces attribution after that clear.Suggested fix direction
issues.state(or useUPDATE … WHERE state = 'CLOSED' RETURNING …) immediately before writingsolved_by_pr.fetchIssueClosingPrentirely if the issue is no longer closed after the GraphQL call returns.Impact
solved_by_prfeeds issue-discovery scoring. An OPEN issue with a solver link can incorrectly count as solved or skew credibility ratios until a later webhook/backfill corrects it.