{"record":{"id":"d8ca6802d84bcc13","repo":"affaan-m/ECC","slug":"issue-issue-number-is-already-claimed-by-sta","errorCode":null,"errorMessage":"Issue #${issue.number} is already claimed by ${state.owner || 'unknown'}","messagePattern":"Issue #(.+?) is already claimed by (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/github-coordination/state.js","lineNumber":215,"sourceCode":"    validation: state.validation || 'pending',\n    review: state.review || 'not-requested',\n    project: summarizeProjectProjection(state, policy),\n    dependencies: Array.isArray(state.dependencies) ? state.dependencies : [],\n    tasks: Array.isArray(state.tasks) ? state.tasks : [],\n    labels: normalizeLabels(issue.labels),\n    workItemId: `github-${slugifySegment(repo)}-epic-${issue.number}`,\n    lastActionAt: state.lastActionAt || null,\n    lastSyncAt: state.lastSyncAt || null,\n  };\n}\n\nfunction assertIssueClaimable(issue, state) {\n  if (String(issue.state || '').toLowerCase() !== 'open') {\n    throw new Error(`Issue #${issue.number} is not open`);\n  }\n\n  if (state.status === 'claimed') {\n    throw new Error(`Issue #${issue.number} is already claimed by ${state.owner || 'unknown'}`);\n  }\n}\n\nfunction verifyDependenciesClosed(repo, dependencyNumbers, options = {}, allIssues = null) {\n  if (!Array.isArray(dependencyNumbers) || dependencyNumbers.length === 0) {\n    return [];\n  }\n\n  const issueList = allIssues || listIssues(repo, { ...options, state: 'all', limit: options.limit || 200 });\n  const closed = [];\n  for (const dependencyNumber of dependencyNumbers) {\n    const issue = findIssueByNumber(issueList, dependencyNumber);\n    if (!issue) {\n      process.stderr.write(`[github-coordination] Warning: dependency issue #${dependencyNumber} not found in issue list (may be in a different repo or beyond limit)\\n`);\n    } else if (String(issue.state || '').toLowerCase() === 'closed') {\n      closed.push(dependencyNumber);\n    }\n  }","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/github-coordination/state.js#L197-L233","documentation":"assertIssueClaimable throws this when the coordination state for an issue has status 'claimed', meaning another agent/process already owns the GitHub issue. The claim-lock mechanism prevents duplicate concurrent work on the same issue; applyClaim calls this check before assigning a new owner. The existing owner is embedded in the message (or 'unknown' if state.owner is empty).","triggerScenarios":"Calling applyClaim(repo, issueNumber, owner) while state.js on disk already records { status: 'claimed', owner: <someone> } for that issue; two agents attempting to claim the same issue simultaneously; a stale claim from a previous session that was never released.","commonSituations":"Parallel CI jobs or multiple agents competing for issues from the same queue; a crashed session left the issue marked claimed; a developer locally re-runs a claim script after another teammate already claimed the issue.","solutions":["Pick a different, unclaimed issue from the queue instead of contending for this one.","Contact the listed owner to confirm the claim is still active; if abandoned, have the owner release the claim (reset status in the coordination state) and retry.","Inspect scripts/lib/github-coordination state file for the issue to verify the owner and timestamp; clear stale claims only after confirming the owner is inactive.","Add a retry loop that re-reads state before claiming to reduce races between simultaneous claim attempts."],"exampleFix":"// before: blindly claim\nawait applyClaim(repo, issueNumber, myOwner);\n\n// after: check first\nconst state = readIssueState(repo, issueNumber);\nif (state.status === 'claimed') {\n  console.log(`Skipping #${issueNumber}: owned by ${state.owner}`);\n} else {\n  await applyClaim(repo, issueNumber, myOwner);\n}","handlingStrategy":"try-catch","validationCode":"const state = readIssueState(repo, issueNumber);\nif (state && state.status === 'claimed') {\n  throw new SkipError(`#${issueNumber} claimed by ${state.owner}`);\n}","typeGuard":"function isClaimable(state) {\n  return state != null && typeof state === 'object' && state.status !== 'claimed';\n}","tryCatchPattern":"try {\n  await applyClaim(repo, issueNumber, owner);\n} catch (e) {\n  if (/is already claimed by/.test(e.message)) {\n    console.warn(`Skipping: ${e.message}`);\n    return;\n  }\n  throw e;\n}","preventionTips":["Re-read claim state immediately before claiming to narrow race windows.","Treat 'claimed' as terminal unless the owner explicitly releases it.","Monitor for stale claims (owner inactive beyond a timeout) with a periodic sweeper."],"tags":["github","concurrency","coordination","claim-conflict"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}