affaan-m/ECC · error · Error

Issue #${issue.number} is already claimed by ${state.owner |

Error message

Issue #${issue.number} is already claimed by ${state.owner || 'unknown'}

What it means

Thrown by assertIssueClaimable() when the issue is open but the coordination state already records status === 'claimed'. The message identifies the current owner (or 'unknown' if state.owner is missing) so the caller can coordinate rather than double-claim.

Source

Thrown at scripts/lib/github-coordination/state.js:215

    validation: state.validation || 'pending',
    review: state.review || 'not-requested',
    project: summarizeProjectProjection(state, policy),
    dependencies: Array.isArray(state.dependencies) ? state.dependencies : [],
    tasks: Array.isArray(state.tasks) ? state.tasks : [],
    labels: normalizeLabels(issue.labels),
    workItemId: `github-${slugifySegment(repo)}-epic-${issue.number}`,
    lastActionAt: state.lastActionAt || null,
    lastSyncAt: state.lastSyncAt || null,
  };
}

function assertIssueClaimable(issue, state) {
  if (String(issue.state || '').toLowerCase() !== 'open') {
    throw new Error(`Issue #${issue.number} is not open`);
  }

  if (state.status === 'claimed') {
    throw new Error(`Issue #${issue.number} is already claimed by ${state.owner || 'unknown'}`);
  }
}

function verifyDependenciesClosed(repo, dependencyNumbers, options = {}, allIssues = null) {
  if (!Array.isArray(dependencyNumbers) || dependencyNumbers.length === 0) {
    return [];
  }

  const issueList = allIssues || listIssues(repo, { ...options, state: 'all', limit: options.limit || 200 });
  const closed = [];
  for (const dependencyNumber of dependencyNumbers) {
    const issue = findIssueByNumber(issueList, dependencyNumber);
    if (!issue) {
      process.stderr.write(`[github-coordination] Warning: dependency issue #${dependencyNumber} not found in issue list (may be in a different repo or beyond limit)\n`);
    } else if (String(issue.state || '').toLowerCase() === 'closed') {
      closed.push(dependencyNumber);
    }
  }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Confirm the current owner: read the coordination block in the issue body or call getCoordinationState(issue).
  2. If the prior claim is stale, release it first (set status back to 'available' and post the coordination comment) before retrying.
  3. If actively owned, pick a different issue or wait for the owner to publish/release.
  4. Add a pre-check: if (state.status === 'claimed') skip or queue.

Example fix

// before
assertIssueClaimable(issue, state);

// after
if (state.status === 'claimed') {
  console.log(`#${issue.number} already owned by ${state.owner || 'unknown'}; skipping`);
  return null;
}
assertIssueClaimable(issue, state);
Defensive patterns

Strategy: validation

Validate before calling

function isClaimable(issue, state) {
  return String(issue.state || '').toLowerCase() === 'open'
    && state.status !== 'claimed';
}
if (!isClaimable(issue, state)) {
  console.log(`#${issue.number} owned by ${state.owner || 'unknown'}`);
}

Type guard

function isUnclaimed(state) {
  return state && state.status !== 'claimed';
}

Try / catch

try {
  assertIssueClaimable(issue, state);
} catch (err) {
  if (/already claimed/) {
    return { skipped: true, owner: state.owner };
  }
  throw err;
}

Prevention

When it happens

Trigger: assertIssueClaimable(issue, state) where state.status === 'claimed'. state is typically returned by getCoordinationState() which parses the embedded coordination block from the issue body.

Common situations: Two agents race to claim the same issue; a previous claim's comment is still embedded in the issue body; the claim was released locally but the issue body still shows status: claimed; manual edit of the coordination marker left status as 'claimed'.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/d8ca6802d84bcc13. Report an issue: GitHub.