{"record":{"id":"c8ff844bab13969a","repo":"ruvnet/ruflo","slug":"validation-error","errorCode":"VALIDATION_ERROR","errorMessage":"At least one reviewer is required","messagePattern":"At least one reviewer is required","errorType":"exception","errorClass":"ClaimOperationError","httpStatus":null,"severity":"warning","filePath":"v3/@claude-flow/claims/src/application/claim-service.ts","lineNumber":523,"sourceCode":"      issueId,\n      previousStatus,\n      status,\n      note\n    );\n    await this.eventStore.append(statusEvent);\n  }\n\n  async requestReview(issueId: string, reviewers: Claimant[]): Promise<void> {\n    const claim = await this.claimRepository.findByIssueId(issueId);\n\n    // Validate claim exists\n    if (!claim) {\n      throw new ClaimOperationError('NOT_CLAIMED', `Issue ${issueId} is not claimed`);\n    }\n\n    // Validate at least one reviewer\n    if (!reviewers || reviewers.length === 0) {\n      throw new ClaimOperationError('VALIDATION_ERROR', 'At least one reviewer is required');\n    }\n\n    // Validate reviewers exist\n    for (const reviewer of reviewers) {\n      const exists = await this.claimantRepository.exists(reviewer.id);\n      if (!exists) {\n        throw new ClaimOperationError(\n          'CLAIMANT_NOT_FOUND',\n          `Reviewer ${reviewer.name} not found`\n        );\n      }\n    }\n\n    // Update claim\n    const previousStatus = claim.status;\n    claim.reviewers = reviewers;\n    claim.status = 'in_review';\n    claim.lastActivityAt = new Date();","sourceCodeStart":505,"sourceCodeEnd":541,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/application/claim-service.ts#L505-L541","documentation":"Thrown by requestReview when the reviewers argument is null, undefined, or an empty array. This is pure input validation that runs before any repository lookup, so it indicates a caller bug, not a system state problem.","triggerScenarios":"Passing [] or undefined as reviewers; a filter/compute step upstream reduced the reviewer list to zero entries before the call.","commonSituations":"Reviewer list computed from a query that returned no matches; optional parameter left unset; refactoring that changed the reviewers source.","solutions":["Guard the call site: only invoke requestReview when reviewers && reviewers.length > 0.","Fall back to a known default reviewer when the computed list is empty.","If no reviewers are available, skip the review stage or surface a configuration error upstream.","Add a unit test asserting reviewers is non-empty at the boundary."],"exampleFix":"// before\nawait claimService.requestReview(issueId, reviewersFromQuery); // [] -> throws\n\n// after\nif (reviewersFromQuery && reviewersFromQuery.length > 0) {\n  await claimService.requestReview(issueId, reviewersFromQuery);\n} else {\n  throw new Error('No reviewers available; refusing to request review');\n}","handlingStrategy":"validation","validationCode":"if (!reviewers || reviewers.length === 0) {\n  throw new Error('Cannot request review: reviewers list is empty');\n}\nawait claimService.requestReview(issueId, reviewers);","typeGuard":"function hasReviewers(list: Claimant[] | null | undefined): list is Claimant[] {\n  return Array.isArray(list) && list.length > 0;\n}","tryCatchPattern":"try {\n  await claimService.requestReview(issueId, reviewers);\n} catch (e) {\n  if (e instanceof ClaimOperationError && e.code === 'VALIDATION_ERROR') {\n    // surface as a 400 to the caller; this is a caller bug, not transient\n    throw new BadRequestError(e.message);\n  }\n  throw e;\n}","preventionTips":["Add a unit test at the call site asserting reviewers.length > 0.","Default to a known reviewer role when the computed list is empty, or fail loudly upstream.","Treat VALIDATION_ERROR as a programming error, not a retryable condition."],"tags":["claims","review","input-validation","caller-bug"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}