Skip to content

[bug] ISSUE_CLOSURE job can write solved_by_pr onto a reopened (OPEN) issue after async GraphQL fetch #199

Description

@andriypolanski

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

  1. 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}.

  2. Before the job finishes its GraphQL timeline fetch (fetchIssueClosingPr), reopen the issue on GitHub.

  3. The issues.reopened webhook runs first: upserts state = OPEN, solved_by_pr = null.

  4. The already-running ISSUE_CLOSURE job still holds a stale in-memory read from step 1 (state = CLOSED) and continues into fetchIssueClosingPr.

  5. When the job completes, it executes:

    await this.issueRepo.update({ repoFullName, issueNumber }, { solvedByPr });

    with no state guard.

  6. 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.tshandleIssueClosure() 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions