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
- Clamp redemptionStart to at minimum yesterday: max(requestedStart, today.minus(1 day)).
- If historical lookups are required, use a different API or change the policy constant; do not bypass the check.
- 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
- Clamp requested start dates to the policy minimum before calling the API
- Inject a consistent Clock in tests matching the fixtures' dates
- Communicate the maximum lookback window to API consumers
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
- end of range too far in the future
- end of range must be after start of range
- timestamps must be day aligned
- redemption window too large
- Blank header
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)