{"record":{"id":"c046dcf4c17e0059","repo":"neondatabase/neon","slug":"received-failure-response-for-copy-from-from-to","errorCode":null,"errorMessage":"Received failure response for copy from {from} to {to}.","messagePattern":"Received failure response for copy from (.+?) to (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/remote_storage/src/azure_blob.rs","lineNumber":906,"sourceCode":"            let source_url = format!(\n                \"{}/{}\",\n                self.client.url()?,\n                self.relative_path_to_name(from)\n            );\n\n            let builder = blob_client.copy(Url::from_str(&source_url)?);\n            let copy = builder.into_future();\n\n            let result = copy.await?;\n\n            copy_status = Some(result.copy_status);\n            loop {\n                match copy_status.as_ref().expect(\"we always set it to Some\") {\n                    CopyStatus::Aborted => {\n                        anyhow::bail!(\"Received abort for copy from {from} to {to}.\");\n                    }\n                    CopyStatus::Failed => {\n                        anyhow::bail!(\"Received failure response for copy from {from} to {to}.\");\n                    }\n                    CopyStatus::Success => return Ok(()),\n                    CopyStatus::Pending => (),\n                }\n                // The copy is taking longer. Waiting a second and then re-trying.\n                // TODO estimate time based on copy_progress and adjust time based on that\n                tokio::time::sleep(Duration::from_millis(1000)).await;\n                let properties = blob_client.get_properties().into_future().await?;\n                let Some(status) = properties.blob.properties.copy_status else {\n                    tracing::warn!(\"copy_status for copy is None!, from={from}, to={to}\");\n                    return Ok(());\n                };\n                copy_status = Some(status);\n            }\n        };\n\n        let res = tokio::select! {\n            res = op => res,","sourceCodeStart":888,"sourceCodeEnd":924,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/azure_blob.rs#L888-L924","documentation":"CopyStatus::Failed from Azure's async Copy Blob: the copy was attempted but the service could not read from the source. Typical causes: no read permission on the source (SAS missing read scope), the source was deleted before the copy service read it, or the source blob is in the Archive access tier (archived blobs cannot be copied without rehydration).","triggerScenarios":"Copying a source the credentials cannot read (cross-account or cross-container copy without a read-scoped source SAS); source deleted between the copy request and the server reading it; source blob in Archive tier; source lease/retention states preventing reads.","commonSituations":"Lifecycle policies moving blobs to Archive tier; copying across storage accounts with insufficient SAS scopes; GC deleting source objects concurrently with copy operations.","solutions":["Verify read access on the source: the SAS/identity needs read on the source container or blob, not just write on the destination","Check the source still exists and is not in Archive tier — rehydrate it or copy from a Hot/Cool replica","If the source is gone, propagate failure upstream instead of retrying","For cross-account copies use a source URL that embeds a read-scoped SAS token"],"exampleFix":"// before: copy with a bare source URL (requires read on source, fails cross-account)\nlet builder = blob_client.copy(Url::from_str(&source_url)?);\n\n// after: copy from a source URL carrying a read-scoped SAS\nlet source_url = format!(\"{}/{}?{}\", src_account_url, src_blob_name, src_read_sas);\nlet builder = blob_client.copy(Url::from_str(&source_url)?);","handlingStrategy":"try-catch","validationCode":"// Verify the source is readable (and not archived) before issuing the server-side copy.\nasync fn source_copyable(storage: &GenericRemoteStorage, from: &RemotePath, cancel: &CancellationToken) -> anyhow::Result<()> {\n    match storage.head_object(from.get_path().to_string(), cancel).await {\n        Ok(meta) => {\n            // rejections for archived sources are reported as access errors downstream\n            let _ = meta;\n            Ok(())\n        }\n        Err(DownloadError::NotFound) => anyhow::bail!(\"source vanished before copy\"),\n        Err(e) => Err(e.into()),\n    }\n}","typeGuard":null,"tryCatchPattern":"// Failure is often permanent (permissions/archive): inspect before retrying.\nmatch storage.copy(&from, &to, &cancel).await {\n    Ok(()) => Ok(()),\n    Err(e) => {\n        let msg = format!(\"{e:#}\");\n        if msg.contains(\"Received failure response for copy\") {\n            // check source access + tier, fix, then a single retry — do not blind-retry\n            verify_source_access(&from).await?;\n            storage.copy(&from, &to, &cancel).await\n        } else {\n            Err(e)\n        }\n    }\n}","preventionTips":["Grant read scope on the source in addition to write on the destination (SAS/identity)","Exclude Archive-tier blobs from copy pipelines or rehydrate them first","Delete-proof sources (defer GC) while copies reference them"],"tags":["azure","blob-storage","server-side-copy","permissions"],"backgroundTag":"server-side-copy-failed","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}