affaan-m/ECC · error · Error
Work item is already done; cannot claim.
Error message
Work item ${target.id} is already done; cannot claim. What it means
claimWorkItem only claims items in an open status. If selectClaimTarget resolves a target whose status is not open (e.g. done), it throws 'Work item <id> is already done; cannot claim.' This prevents re-claiming completed work.
Solutions
- Verify the item's status via store.getWorkItemById(id) before claiming.
- Pick a different, still-open work item — the item is finished and should not be re-claimed.
- If the item genuinely needs more work, create a new follow-up work item instead of re-claiming the done one.
- Refresh control-pane state; you may be looking at stale data.
Example fix
// before
claimWorkItem(store, { id: 'item-7', owner: 'agent' }); // item-7 is done
// after
const item = store.getWorkItemById('item-7');
if (item && !isOpenStatus(item.status)) {
store.upsertWorkItem({ id: 'item-7-followup', title: 'Rework: ' + item.title, status: 'ready' });
}
claimWorkItem(store, { id: 'item-7-followup', owner: 'agent' }); Defensive patterns
Strategy: try-catch
Validate before calling
const item = store.getWorkItemById(id);
if (item && !isOpenStatus(item.status)) throw new Error(`Skip ${id}: status ${item.status}`); Type guard
function isClaimable(item) { return !!item && isOpenStatus(item.status); } Try / catch
try { claimWorkItem(store, { id, owner }); } catch (e) {
if (e.message.includes('already done')) { /* pick next open item */ }
else throw e;
} Prevention
- Filter to open-status items before presenting claim candidates.
- Re-read item state immediately before claiming to narrow race windows.
- Never re-claim done items — create follow-up items instead.
- Refresh dashboard data before auto-claim runs.
When it happens
Trigger: Explicitly claiming an item by id whose status is done (or any non-open status); race where two agents try to claim and one already finished the item.
Common situations: Stale dashboard id claimed after the item was completed; re-running a claim script against a finished backlog; auto-pick returning nothing vs explicit id pointing at a done card.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- assigneeKind must be 'agent' or 'human'.
- claim requires an owner.
- Invalid install-state
- Invalid : expected a non-empty JSON object
- Invalid lane ' '. Expected one of .
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/75caacc4ff4aaf48.
Report an issue: GitHub.
Appendix: 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 8321021c54)