signalapp/Signal-Server · error · IOException

Registration service failure

Error message

Registration service failure

What it means

PhoneVerificationTokenManager wraps calls to the external registration service (gRPC). When the remote verify call fails with a gRPC status other than INVALID_ARGUMENT (e.g. UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL), the error is logged and rethrown as an IOException so callers can treat it as a transient backend failure rather than a client mistake.

Solutions

  1. Check registration service health/availability and its logs for the correlation error logged alongside this message.
  2. Verify the registration service URI/credentials in the service configuration and network connectivity/DNS from this host.
  3. Retry the request after the transient condition clears; wrap the IOException in retry-with-backoff logic at the resource layer.
  4. Confirm the gRPC call's deadline is generous enough for production latency.

Example fix

// before
try {
  phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
  return Response.status(503).build();
}
// after
try {
  phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
  logger.warn("registration service transient failure, retrying", e);
  return retryWithBackoff(() -> phoneVerificationTokenManager.verify(...));
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

try {
  phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
  if (e.getCause() instanceof StatusRuntimeException sre && isRetryable(sre.getStatus().getCode())) {
    scheduleRetryWithBackoff();
  } else {
    return Response.status(503).build();
  }
}

Prevention

When it happens

Trigger: Calling PhoneVerificationTokenManager.verify() -> verifyBySessionId() with a valid session id while the registration service is down, unreachable, times out, or returns an unexpected gRPC error code.

Common situations: Registration service outage or deployment; network partition between Signal service and registration service; gRPC deadline exceeded under load; misconfigured registration service endpoint in production configuration.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/PhoneVerificationTokenManager.java:110

      throws UnverifiedRegistrationSessionException, InvalidRegistrationSessionException, IOException {
    try {
      final RegistrationServiceSession session = registrationServiceClient
          .getSession(sessionId, REGISTRATION_RPC_TIMEOUT)
          .orElseThrow(UnverifiedRegistrationSessionException::new);

      if (!MessageDigest.isEqual(number.getBytes(), session.number().getBytes())) {
        throw new InvalidRegistrationSessionException("number does not match session");
      }
      if (!session.verified()) {
        throw new UnverifiedRegistrationSessionException();
      }
    } catch (final StatusRuntimeException e) {
      if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) {
        throw new InvalidRegistrationSessionException(e.getMessage());
      }

      logger.error("Registration service failure", e);
      throw new IOException("Registration service failure", e);
    }
  }

  private void verifyByRecoveryPassword(
      final String number,
      @Nullable final String userAgent,
      @Nullable final String acceptLanguage,
      @Nullable final String mostRecentProxy,
      final byte[] recoveryPassword)
      throws InterruptedException, IOException, RecoveryPasswordVerificationFailedException {

    if (!registrationRecoveryChecker.checkRegistrationRecoveryAttempt(number, userAgent, acceptLanguage, mostRecentProxy)) {
      throw new RecoveryPasswordVerificationFailedException();
    }

    try {
      final UUID phoneNumberIdentifier = phoneNumberIdentifiers.getPhoneNumberIdentifier(number)
          .get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);

View on GitHub (pinned to 100ab61c82)