{"record":{"id":"badc839f973610be","repo":"ruvnet/ruflo","slug":"only-the-original-claimant-can-contest-the-steal","errorCode":null,"errorMessage":"Only the original claimant can contest the steal","messagePattern":"Only the original claimant can contest the steal","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/claims/src/application/work-stealing-service.ts","lineNumber":382,"sourceCode":"\n    // Check if there's a valid contest window\n    if (!claim.contestInfo) {\n      throw new Error('No steal to contest - issue was not recently stolen');\n    }\n\n    if (claim.contestInfo.resolution) {\n      throw new Error('Contest has already been resolved');\n    }\n\n    const nowMs = this.nowMs();\n    const windowEndsAtMs = new Date(claim.contestInfo.windowEndsAt).getTime();\n    if (nowMs > windowEndsAtMs) {\n      throw new Error('Contest window has expired');\n    }\n\n    // Verify the contester was the original owner\n    if (claim.contestInfo.contestedBy.id !== originalClaimant.id) {\n      throw new Error('Only the original claimant can contest the steal');\n    }\n\n    // Update contest info with reason\n    claim.contestInfo.reason = reason;\n    claim.contestInfo.contestedAt = new Date(nowMs);\n\n    await this.repository.update(claim);\n\n    // Emit contest event\n    await this.emitContestEvent(claim);\n  }\n\n  // ===========================================================================\n  // Resolve Contest\n  // ===========================================================================\n\n  /**\n   * Resolve a contest (queen or human decides the winner)","sourceCodeStart":364,"sourceCodeEnd":400,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/application/work-stealing-service.ts#L364-L400","documentation":"Plain Error thrown by contestSteal when originalClaimant.id does not equal claim.contestInfo.contestedBy.id. contestInfo.contestedBy records the claimant from whom the work was stolen; only that exact claimant (by id) is permitted to contest. This is an authorization guard, not a state problem.","triggerScenarios":"A different agent/user attempting to contest; a coordinator passing its own id instead of the original owner's; id format mismatch (e.g. 'agent-7' vs 'agent-007'); wrong Claimant object passed from a stale cache.","commonSituations":"Coordinator attempts to contest on behalf of an agent but uses its own identity; id source differs between steal-time recording and contest-time lookup; case or prefix differences in ids.","solutions":["Pass the exact Claimant whose id matches claim.contestInfo.contestedBy.id.","If you are a queen/coordinator adjudicating rather than contesting, use resolveContest instead.","Verify the id source and formatting (trim, casing, prefixes) against the recorded contestedBy.","Read contestedBy off the claim and reuse that object rather than reconstructing it."],"exampleFix":"// before\nawait workStealing.contestSteal(issueId, coordinator, reason); // not the victim -> throws\n\n// after\nconst claim = await repo.findByIssueId(issueId);\nconst victim = claim?.contestInfo?.contestedBy;\nif (victim) {\n  await workStealing.contestSteal(issueId, victim, reason);\n} else {\n  // coordinator adjudicates instead\n  await workStealing.resolveContest(issueId, winner, reason);\n}","handlingStrategy":"validation","validationCode":"const claim = await repository.findByIssueId(issueId);\nconst victim = claim?.contestInfo?.contestedBy;\nif (!victim || victim.id !== originalClaimant.id) {\n  throw new Error('Only the original claimant may contest');\n}\nawait workStealing.contestSteal(issueId, victim, reason);","typeGuard":"function isContestVictim(claimant: Claimant, claim: IssueClaimWithStealing | null): boolean {\n  return !!claim?.contestInfo?.contestedBy && claim.contestInfo.contestedBy.id === claimant.id;\n}","tryCatchPattern":"try {\n  await workStealing.contestSteal(issueId, originalClaimant, reason);\n} catch (e) {\n  if (e instanceof Error && /Only the original claimant/.test(e.message)) {\n    // caller is not the victim; coordinators should use resolveContest instead\n    return;\n  }\n  throw e;\n}","preventionTips":["Read contestedBy off the claim and reuse that exact object as the contesting claimant.","Coordinators adjudicating a contest should call resolveContest, not contestSteal.","Normalize claimant ids (casing/prefix) at all boundaries to avoid spurious mismatches."],"tags":["work-stealing","contest","authorization","id-mismatch","plain-error"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}