signalapp/Signal-Server · error · IllegalArgumentException

start of range too far in the past

Error message

start of range too far in the past

What it means

RedemptionRange.inclusive() rejects ranges whose start is earlier than yesterday (today truncated minus one day, evaluated on the provided Clock). This bounds how far back a receipt-redemption query may reach.

Solutions

  1. Clamp redemptionStart to at minimum yesterday: max(requestedStart, today.minus(1 day)).
  2. If historical lookups are required, use a different API or change the policy constant; do not bypass the check.
  3. In tests, inject a frozen Clock consistent with the instants under test.

Example fix

// before
RedemptionRange.inclusive(clock, weekAgo, today);
// after
Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
Instant start = Instant.from(weekAgo).isBefore(today.minus(Duration.ofDays(1)))
    ? today.minus(Duration.ofDays(1)) : weekAgo;
RedemptionRange.inclusive(clock, start, today);
Defensive patterns

Strategy: validation

Validate before calling

Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
if (start.isBefore(today.minus(Duration.ofDays(1)))) {
  start = today.minus(Duration.ofDays(1));
}

Type guard

boolean isStartWithinPolicy(Instant start, Clock clock) {
  return !start.isBefore(clock.instant().truncatedTo(ChronoUnit.DAYS).minus(Duration.ofDays(1)));
}

Try / catch

try {
  RedemptionRange.inclusive(clock, start, end);
} catch (IllegalArgumentException e) {
  if (e.getMessage().contains("too far in the past")) {
    return redeemMostRecentAllowedWindow(clock);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling inclusive() with a redemptionStart more than one day before the clock's current (truncated) day, e.g. querying a week-old redemption window.

Common situations: Backfill jobs trying to redeem old receipts; clients caching old start dates; unit tests using fixed instants far in the past while the injected Clock is real-time.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/6dbbb64358c073bd. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/RedemptionRange.java:64

  /// @return A {@link RedemptionRange} that can be used to iterate each day between `redemptionStart` and
  ///  `redemptionEnd`
  /// @throws IllegalArgumentException if the redemption bounds were not valid
  public static RedemptionRange inclusive(Clock clock, Instant redemptionStart, Instant redemptionEnd)
      throws IllegalArgumentException {
    final Instant today = clock.instant().truncatedTo(ChronoUnit.DAYS);
    final Instant yesterday = today.minus(Duration.ofDays(1));

    if (redemptionStart.isAfter(redemptionEnd)) {
      throw new IllegalArgumentException("end of range must be after start of range");
    }

    if (!redemptionStart.truncatedTo(ChronoUnit.DAYS).equals(redemptionStart)
        || !redemptionEnd.truncatedTo(ChronoUnit.DAYS).equals(redemptionEnd)) {
      throw new IllegalArgumentException("timestamps must be day aligned");
    }

    if (redemptionStart.isBefore(yesterday)) {
      throw new IllegalArgumentException("start of range too far in the past");
    }

    if (redemptionEnd.isAfter(today.plus(MAX_REDEMPTION_DURATION).plus(Duration.ofDays(1)))) {
      throw new IllegalArgumentException("end of range too far in the future");
    }

    if (redemptionEnd.isAfter(redemptionStart.plus(MAX_REDEMPTION_DURATION))) {
      throw new IllegalArgumentException("redemption window too large");
    }

    return new RedemptionRange(
        LocalDate.ofInstant(redemptionStart, ZoneOffset.UTC),
        LocalDate.ofInstant(redemptionEnd, ZoneOffset.UTC));
  }

  @Override
  public @NotNull Iterator<Instant> iterator() {
    final Instant fromInstant = from.atStartOfDay(ZoneOffset.UTC).toInstant();

View on GitHub (pinned to 100ab61c82)