affaan-m/ECC · warning · Error
Work item ${target.id} is already done; cannot claim.
Error message
Work item ${target.id} is already done; cannot claim. What it means
Thrown by claimWorkItem when the resolved target item has a status in DONE_STATUSES (done, closed, resolved, merged, cancelled). Once a card is in a terminal lane it cannot be claimed again — claiming implies moving it to 'running', which would resurrect completed work and corrupt the board history. This differs from the no-id queue path, which simply skips done items via isOpenStatus.
Source
Thrown at scripts/lib/control-pane/work-item-mutations.js:69
/**
* Claim an unassigned work item for an agent or human. Sets the owner (and
* optional assigneeKind) and moves the card to running unless an explicit
* status is supplied. Returns { claimed, item } or { claimed: false, reason }.
*/
function claimWorkItem(store, { id, owner, assigneeKind, sessionId, status } = {}) {
if (!owner) {
throw new Error('claim requires an owner.');
}
const kind = assigneeKind ? String(assigneeKind).toLowerCase() : null;
if (kind && !VALID_ASSIGNEE_KINDS.has(kind)) {
throw new Error("assigneeKind must be 'agent' or 'human'.");
}
const target = selectClaimTarget(store, { id });
if (!target) {
return { claimed: false, reason: 'no-unassigned-open-items' };
}
if (!isOpenStatus(target.status)) {
throw new Error(`Work item ${target.id} is already done; cannot claim.`);
}
const metadata = { ...(target.metadata || {}) };
if (kind) {
metadata.assigneeKind = kind;
}
const item = store.upsertWorkItem({
...target,
owner,
sessionId: sessionId ?? target.sessionId ?? null,
status: status ?? 'running',
metadata,
updatedAt: new Date().toISOString()
});
return { claimed: true, item };
}
/**
* Move a work item to a kanban lane (ready | running | blocked | done).View on GitHub (pinned to 01e15490f0)
Solutions
- Refresh and re-check the item's status before claiming; if done, abort or pick a different item.
- Wrap claim in try/catch and treat this specific message as a benign 'already finished' signal rather than an error.
- For the JIT queue (no id), this never happens because selectClaimTarget filters by isOpenStatus — prefer omitting id.
- Reduce the list→claim window to minimize TOCTOU races.
Example fix
// before
claimWorkItem(store, { id, owner });
// after — check freshness, treat done as skip
const fresh = store.getWorkItemById(id);
if (fresh && ['done','closed','resolved','merged','cancelled'].includes(String(fresh.status).toLowerCase())) {
return { claimed: false, reason: 'already-done' };
}
claimWorkItem(store, { id, owner }); Defensive patterns
Strategy: try-catch
Validate before calling
const DONE = new Set(['done','closed','resolved','merged','cancelled']);
if (id) {
const item = store.getWorkItemById(id);
if (item && DONE.has(String(item.status).toLowerCase())) {
return { claimed: false, reason: 'already-done' };
}
} Type guard
function isOpenItem(item) {
const DONE = new Set(['done','closed','resolved','merged','cancelled']);
return item && !DONE.has(String(item.status).toLowerCase());
} Try / catch
try {
claimWorkItem(store, { id, owner });
} catch (e) {
if (/already done/.test(e.message)) { return { claimed: false, reason: 'already-done' }; }
throw e;
} Prevention
- Re-check status immediately before claiming to shrink the TOCTOU window.
- Prefer the JIT queue (omit id) which auto-skips done items.
- Treat 'already done' as a benign skip, not a hard error.
When it happens
Trigger: Calling claimWorkItem with an explicit id whose item was closed/done between the list read and the claim (TOCTOU); claiming by id that another worker already moved to done; UI showing a stale card that was resolved.
Common situations: Race between two agents: one finishes (moves to done) while the other tries to claim; a stale board view; manual status edit set the item to 'done' out of band.
Related errors
- Work item not found: ${id}
- claim requires an owner.
- assigneeKind must be 'agent' or 'human'.
- move requires a work item id.
- Invalid lane '${lane}'. Expected one of ${[...VALID_LANES].j
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/75caacc4ff4aaf48.
Report an issue: GitHub.