{"record":{"id":"5de2275d37047f6c","repo":"signalapp/Signal-Server","slug":"redemption-window-too-large","errorCode":null,"errorMessage":"redemption window too large","messagePattern":"redemption window too large","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java","lineNumber":72,"sourceCode":"    if (redemptionStart.isAfter(redemptionEnd)) {\n      throw new IllegalArgumentException(\"end of range must be after start of range\");\n    }\n\n    if (!redemptionStart.truncatedTo(ChronoUnit.DAYS).equals(redemptionStart)\n        || !redemptionEnd.truncatedTo(ChronoUnit.DAYS).equals(redemptionEnd)) {\n      throw new IllegalArgumentException(\"timestamps must be day aligned\");\n    }\n\n    if (redemptionStart.isBefore(yesterday)) {\n      throw new IllegalArgumentException(\"start of range too far in the past\");\n    }\n\n    if (redemptionEnd.isAfter(today.plus(MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1)))) {\n      throw new IllegalArgumentException(\"end of range too far in the future\");\n    }\n\n    if (redemptionEnd.isAfter(redemptionStart.plus(MAX_REDEMPTION_DURATION))) {\n      throw new IllegalArgumentException(\"redemption window too large\");\n    }\n\n    return new RedemptionRange(\n        LocalDate.ofInstant(redemptionStart, ZoneOffset.UTC),\n        LocalDate.ofInstant(redemptionEnd, ZoneOffset.UTC));\n  }\n\n  @Override\n  public @NotNull Iterator<Instant> iterator() {\n    final Instant fromInstant = from.atStartOfDay(ZoneOffset.UTC).toInstant();\n    final Instant endInstant = end.atStartOfDay(ZoneOffset.UTC).toInstant();\n    return Stream\n        .iterate(fromInstant, redemptionTime -> redemptionTime.plus(Duration.ofDays(1)))\n        .takeWhile(redemptionTime -> !redemptionTime.isAfter(endInstant))\n        .iterator();\n  }\n\n  @Override","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java#L54-L90","documentation":"RedemptionRange.inclusive() enforces a maximum window width: end minus start must not exceed MAX_REDEMPTION_DURATION, regardless of where the range sits relative to today. The error indicates the requested span is too wide.","triggerScenarios":"Calling inclusive() with redemptionEnd > redemptionStart + MAX_REDEMPTION_DURATION, e.g. a 60-day window when the maximum is a few days.","commonSituations":"Export/reporting jobs requesting long history at once; clients multiplying days incorrectly; policy change reducing MAX_REDEMPTION_DURATION while callers still request old spans.","solutions":["Shrink the requested window to at most MAX_REDEMPTION_DURATION and paginate/iterate over multiple ranges.","Cap the end: redemptionEnd = min(requestedEnd, redemptionStart.plus(MAX_REDEMPTION_DURATION)).","Read the current constant value from source to confirm the allowed span after upgrades."],"exampleFix":"// before\nRedemptionRange.inclusive(clock, start, start.plus(Duration.ofDays(30)));\n// after\nInstant end = start.plus(Duration.ofDays(30));\nwhile (!start.isAfter(requestedEnd)) {\n  RedemptionRange.inclusive(clock, start, end.minusSeconds(1));\n  start = end;\n  end = start.plus(MAX_REDEMPTION_DURATION);\n}","handlingStrategy":"validation","validationCode":"if (end.minusSeconds(0).isAfter(start.plus(MAX_REDEMPTION_DURATION))) {\n  end = start.plus(MAX_REDEMPTION_DURATION);\n}","typeGuard":"boolean windowFits(Instant start, Instant end) {\n  return !end.isAfter(start.plus(MAX_REDEMPTION_DURATION));\n}","tryCatchPattern":"try {\n  RedemptionRange.inclusive(clock, start, end);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"window too large\")) {\n    iterateWindows(start, end, MAX_REDEMPTION_DURATION);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Chunk large date spans into windows of at most MAX_REDEMPTION_DURATION","Centralize range construction in one utility that enforces all policy bounds","Test range construction against the current policy constant after upgrades"],"tags":["validation","date-range","window-size"],"backgroundTag":"value-out-of-range","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}