{"record":{"id":"556d97e2364411e5","repo":"ruvnet/ruflo","slug":"issue-issueid-is-not-claimed","errorCode":null,"errorMessage":"Issue ${issueId} is not claimed","messagePattern":"Issue (.+?) is not claimed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/claim-service.ts","lineNumber":302,"sourceCode":"\n    this.claims.set(issueId, claim);\n    await this.saveClaims();\n\n    this.emitEvent({\n      type: 'issue:claimed',\n      timestamp: now,\n      issueId,\n      claimant,\n      previousClaimant: existing?.claimant,\n    });\n\n    return { success: true, claim };\n  }\n\n  async release(issueId: string, claimant: Claimant): Promise<void> {\n    const claim = this.claims.get(issueId);\n    if (!claim) {\n      throw new Error(`Issue ${issueId} is not claimed`);\n    }\n\n    if (!this.isSameClaimant(claim.claimant, claimant)) {\n      throw new Error(`Issue ${issueId} is not claimed by ${this.formatClaimant(claimant)}`);\n    }\n\n    this.claims.delete(issueId);\n    this.stealableInfo.delete(issueId);\n    await this.saveClaims();\n\n    this.emitEvent({\n      type: 'issue:released',\n      timestamp: new Date(),\n      issueId,\n      claimant,\n    });\n  }\n","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/services/claim-service.ts#L284-L320","documentation":"ClaimService.release(issueId, claimant) looks up the issue in the in-memory claims map and throws this when no claim exists for issueId at all. There is nothing to release: either the issue was never claimed, the claim was already released, or it moved (stolen / handed off) and this service instance has not observed it. The claim-ownership mismatch case is a separate follow-up error ('not claimed by ...').","triggerScenarios":"Calling release() twice for the same issue (double release in cleanup code), releasing after the claim was already handed off or completed, releasing an issue id that was never acquired, or using a ClaimService instance whose loaded state is stale relative to the process that holds the claim.","commonSituations":"Idempotent cleanup paths (finally blocks) that release on every exit including retries, two workers racing to release the same issue, CI runs where teardown re-executes, long-lived processes whose claims file was modified by another process.","solutions":["Pre-check with the public API: const claim = await service.getIssueStatus(issueId); if (claim) await service.release(...)","Treat release as idempotent: catch this specific error and ignore it when the end state (unclaimed) is already achieved","If state seems wrong, re-sync from persisted claims (getAllClaims()/event log) before acting"],"exampleFix":"// before\nawait claims.release(issueId, me); // in a finally block -> throws on second pass\n\n// after\nconst existing = await claims.getIssueStatus(issueId);\nif (existing) await claims.release(issueId, me);","handlingStrategy":"try-catch","validationCode":"const claim = await claims.getIssueStatus(issueId);\nif (claim) await claims.release(issueId, claimant);","typeGuard":null,"tryCatchPattern":"try {\n  await claims.release(issueId, claimant);\n} catch (e) {\n  if (e instanceof Error && e.message.endsWith('is not claimed')) return; // already unclaimed — idempotent success\n  throw e;\n}","preventionTips":["Guard release paths with getIssueStatus(issueId) so double-cleanup is a no-op","Release exactly once per claim acquisition, from the finally block that owns it","When state surprises you, consult getEventLog() to see the release/steal that got there first"],"tags":["claim-service","release","state-machine","coordination"],"backgroundTag":"invalid-state-transition","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}