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
- Check the message for missing media and skip/backfill it instead of copying (HTTP 410 is permanent).
- Re-download or request re-sending of the attachment from the sender, then copy the new object.
- Verify the CDN number and key in CopyMediaObject match current CDN layout (migration may have renumbered).
- 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
- Treat HTTP 410 as permanent: skip and backfill, never retry blindly.
- Prune local DB references to expired/deleted attachments before bulk copy.
- HEAD the source object (or batch-check existence) before issuing copy requests.
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
- Invalid length
- unknown cdn
- exceeded maximum uploadLength
- Could not interpret identity key bytes as an EC public key
- Could not interpret bytes as a ZK credential public key
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)