signalapp/Signal-Server · warning · ClientErrorException

Source object not found

Error message

Source object not found

What it means

When the copy outcome is SOURCE_NOT_FOUND, the controller throws ClientErrorException("Source object not found", HTTP 410 GONE): the media object the request asked to copy does not exist on the source CDN, so the backup copy cannot proceed.

Solutions

  1. Check the message for missing media and skip/backfill it instead of copying (HTTP 410 is permanent).
  2. Re-download or request re-sending of the attachment from the sender, then copy the new object.
  3. Verify the CDN number and key in CopyMediaObject match current CDN layout (migration may have renumbered).
  4. If automatic backup copy keeps failing on stale references, prune dead attachment rows from the local database.

Example fix

// before
copy(params); // throws 410 for expired media, aborts whole backup
// after
try {
  copy(params);
} catch (ClientErrorException e) {
  if (e.getResponse().getStatus() == 410) {
    markAttachmentMissing(messageId); // skip and continue
  } else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!sourceObjectExists(cdn, key)) { // HEAD before copy
  markAttachmentMissing(messageId);
  return;
}

Try / catch

try {
  copyMedia(request);
} catch (ClientErrorException e) {
  if (e.getResponse().getStatus() == 410) {
    skipMissingMedia(messageId); // 410 is permanent, do not retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling copyMedia referencing a source CDN object (cdn number + key) that has been deleted, expired, or never uploaded.

Common situations: Source attachments pruned by temporary-message/attachment expiration before backup copy; stale message DB referencing deleted media; wrong CDN number or key after a CDN migration; new device restoring a DB without the media.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/ArchiveController.java:771

      @NotNull
      @Valid final ArchiveController.CopyMediaRequest copyMediaRequest)
      throws BackupFailedZkAuthenticationException, BackupWrongCredentialTypeException, BackupPermissionException, BackupInvalidArgumentException {
    if (account.isPresent()) {
      throw new BadRequestException("must not use authenticated connection for anonymous operations");
    }

    final AuthenticatedBackupUser backupUser =
        backupManager.authenticateBackupUser(presentation.presentation, signature.signature, userAgent);
    final BackupManager.CopyQuota copyQuota =
        backupManager.getCopyQuota(backupUser, List.of(copyMediaRequest.toCopyParameters()), maxAttachmentSize);
    final CopyResult copyResult = backupManager.copyToBackup(copyQuota).next()
            .blockOptional()
            .orElseThrow(() -> new IllegalStateException("Non empty copy request must return result"));
    backupMetrics.updateCopyCounter(copyResult, UserAgentTagUtil.getPlatformTag(userAgent));
    return switch (copyResult.outcome()) {
      case SUCCESS -> new CopyMediaResponse(copyResult.cdn());
      case SOURCE_WRONG_LENGTH -> throw new BadRequestException("Invalid length");
      case SOURCE_NOT_FOUND -> throw new ClientErrorException("Source object not found", Response.Status.GONE);
      case OUT_OF_QUOTA ->
          throw new ClientErrorException("Media quota exhausted", Response.Status.REQUEST_ENTITY_TOO_LARGE);
    };
  }

  public record CopyMediaBatchRequest(
      @Schema(description = "A list of media objects to copy from the attachments CDN to the backup CDN")
      @NotNull
      @Size(min = 1, max = 1000)
      List<@Valid CopyMediaRequest> items) {}

  public record CopyMediaBatchResponse(

      @Schema(description = "Detailed outcome information for each copy request in the batch")
      List<Entry> responses) {

    public record Entry(
        @Schema(description = """

View on GitHub (pinned to 100ab61c82)