{"record":{"id":"d7e6bbc8a47bc74e","repo":"ruvnet/ruflo","slug":"contest-window-has-expired","errorCode":null,"errorMessage":"Contest window has expired","messagePattern":"Contest window has expired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"v3/@claude-flow/claims/src/application/work-stealing-service.ts","lineNumber":377,"sourceCode":"    const claim = await this.repository.findByIssueId(issueId);\n\n    if (!claim) {\n      throw new Error(`Claim not found for issue: ${issueId}`);\n    }\n\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  // ===========================================================================","sourceCodeStart":359,"sourceCodeEnd":395,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/application/work-stealing-service.ts#L359-L395","documentation":"Plain Error thrown by contestSteal when nowMs() exceeds claim.contestInfo.windowEndsAt. The contest window (config.contestWindowMinutes in DEFAULT_WORK_STEALING_CONFIG) starts at steal time; once it elapses, the steal is final and cannot be contested. This is time-based and not retryable, since the window only moves further into the past.","triggerScenarios":"Contesting long after the steal occurred; processing delay (queue backlog, slow human review) longer than the contest window; contestWindowMinutes misconfigured to a value too short for your workflow; clock skew between an HLC and wall clock.","commonSituations":"Default contest window too short for human-in-the-loop review; notifications queued behind a long backlog; HLC physical clock drifted relative to the wall clock used to compute windowEndsAt.","solutions":["Increase config.contestWindowMinutes to fit your review latency.","Process IssueStolen events with higher priority so contests are filed within the window.","Pre-check the window before calling: compare nowMs() to contestInfo.windowEndsAt and skip if expired.","If expired, accept the steal as final or escalate to a manual override (resolveContest by a coordinator, if still permitted)."],"exampleFix":"// before\nawait workStealing.contestSteal(issueId, originalClaimant, reason); // window passed -> throws\n\n// after (config)\nconst workStealing = new WorkStealingService(repo, bus, { contestWindowMinutes: 120 });\n// after (pre-check)\nconst claim = await repo.findByIssueId(issueId);\nif (claim?.contestInfo && Date.now() <= claim.contestInfo.windowEndsAt.getTime()) {\n  await workStealing.contestSteal(issueId, originalClaimant, reason);\n}","handlingStrategy":"validation","validationCode":"const claim = await repository.findByIssueId(issueId);\nconst windowEnd = claim?.contestInfo?.windowEndsAt;\nif (!windowEnd || Date.now() > windowEnd.getTime()) {\n  throw new Error('Contest window expired; steal is final');\n}\nawait workStealing.contestSteal(issueId, originalClaimant, reason);","typeGuard":null,"tryCatchPattern":"try {\n  await workStealing.contestSteal(issueId, originalClaimant, reason);\n} catch (e) {\n  if (e instanceof Error && /window has expired/.test(e.message)) {\n    // not retryable: escalate or accept the steal as final\n    return escalateExpiredContest(issueId);\n  }\n  throw e;\n}","preventionTips":["Set config.contestWindowMinutes large enough for your review latency.","Pre-check nowMs() vs contestInfo.windowEndsAt before calling.","Prioritize IssueStolen event processing so contests are filed in time."],"tags":["work-stealing","contest","time-window","config","plain-error"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}