{"record":{"id":"c3a8ff0bf00f399e","repo":"aeron-io/aeron","slug":"failed-to-fetch-remote-recording-descriptor","errorCode":null,"errorMessage":"failed to fetch remote recording descriptor","messagePattern":"failed to fetch remote recording descriptor","errorType":"exception","errorClass":"TimeoutException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/ReplicationSession.java","lineNumber":475,"sourceCode":"            {\n                throw new TimeoutException(\"failed to list remote recording descriptor\");\n            }\n        }\n        else\n        {\n            final RecordingDescriptorPoller poller = srcArchive.recordingDescriptorPoller();\n            final int fragments = poller.poll();\n\n            if (poller.isDispatchComplete() && poller.remainingRecordCount() > 0)\n            {\n                final String errorMsg = \"unknown src recording id \" + srcRecordingId;\n                state(State.DONE, errorMsg);\n                error(errorMsg, ArchiveException.UNKNOWN_RECORDING);\n            }\n\n            if (0 == fragments && epochClock.time() >= (timeOfLastActionMs + actionTimeoutMs))\n            {\n                throw new TimeoutException(\"failed to fetch remote recording descriptor\");\n            }\n\n            workCount += fragments;\n        }\n\n        return workCount;\n    }\n\n    private int srcRecordingPosition()\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            final long correlationId = aeron.nextCorrelationId();\n            final long controlSessionId = srcArchive.controlSessionId();\n            if (srcArchive.archiveProxy().getRecordingPosition(srcRecordingId, correlationId, controlSessionId))\n            {","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/ReplicationSession.java#L457-L493","documentation":"Thrown by ReplicationSession.replicateDescriptor when, after successfully sending a listRecording request to the source archive, the recording descriptor poller receives zero fragments for longer than actionTimeoutMs, so the remote recording descriptor never arrives. Aeron throws TimeoutException to abort a replication whose source archive is not answering descriptor queries. It is a driver-side timeout of an in-flight archive control request, not a lookup miss (a missing recording is reported separately as 'unknown src recording id').","triggerScenarios":"Calling AeronArchive.startReplication/replicate (or replicate with live merge) where the source archive accepts the listRecording subscription offer but never publishes the descriptor: source archive conductor stalled or dead, control-channel loss (UDP drops on an unreliable control stream), source recording list backing up, or actionTimeoutMs (aeron.archive.replication.connect.timeout style context) too small for a loaded archive.","commonSituations":"Replicating across WAN links with default timeouts; source archive paused by long GC or swapped out; control request/response channels misconfigured (different control endpoints than the replication channel) so responses never return; heavy archive load delaying the conductor; network partitions between source and destination clusters.","solutions":["Verify the source archive is reachable and its conductor is alive (check source archive logs for the listRecording request).","Increase the replication/connect timeout in the Archive client context (actionTimeoutMs / archive context timeouts) to cover the actual network RTT.","Ensure the control request/response channel configuration matches on source and destination and the network path is reliable (consider reliable/TCP control streams or fix packet loss).","Retry the replication; if it always times out at the same step, capture both archives' logs and check descriptor poller activity.","Upgrade Aeron — replication session timeout handling has been tuned across releases."],"exampleFix":"// before\nfinal ArchiveClientContext ctx = new ArchiveClientContext()\n    .aeron(aeron);\n// after: give the replication session a longer control timeout on slow links\nfinal ArchiveClientContext ctx = new ArchiveClientContext()\n    .aeron(aeron)\n    .connectTimeoutNs(TimeUnit.SECONDS.toNanos(30));","handlingStrategy":"retry","validationCode":"// pre-flight: confirm the source recording exists and the archive responds\ntry (AeronArchive probe = AeronArchive.connect(ctx)) {\n    if (!probe.listRecording(srcRecordingId, (c, i) -> {})) {\n        throw new IllegalStateException(\"src recording \" + srcRecordingId + \" not found on source archive\");\n    }\n}","typeGuard":"boolean sourceArchiveReachable(AeronArchive src) {\n    return src != null && src.controlResponsePoller().subscription().isConnected();\n}","tryCatchPattern":"try {\n    replicationSession.doWork();\n} catch (TimeoutException e) {\n    if (e.getMessage().contains(\"failed to fetch remote recording descriptor\")) {\n        log.warn(\"source archive did not answer descriptor query; retrying with longer timeout\");\n        scheduleRetryWithBackoff();\n    }\n}","preventionTips":["Verify the source recording id exists via listRecording before starting replication.","Size actionTimeoutMs larger than the worst-case control RTT between the two archives.","Monitor source archive conductor health and control-channel connectivity.","Alert on control request/response gaps so stalls are caught before replication timeouts."],"tags":["aeron","archive","timeout","replication","network"],"backgroundTag":"request-timeout","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}