{"record":{"id":"2526d3d49c86e717","repo":"clockworklabs/SpacetimeDB","slug":"missing-object","errorCode":null,"errorMessage":"missing object {}","messagePattern":"missing object (.+?)","errorType":"exception","errorClass":"SnapshotError::ReadObject","httpStatus":null,"severity":"error","filePath":"crates/snapshot/src/remote.rs","lineNumber":565,"sourceCode":"\n            self.stats.skipped_object();\n            return Ok(None);\n        }\n\n        if self.try_hardlink(hash).await? {\n            if self.dry_run {\n                return Ok(Some(path));\n            }\n\n            self.stats.hardlinked_object();\n            return Ok(None);\n        }\n\n        if self.dry_run {\n            return Err(SnapshotError::ReadObject {\n                ty,\n                source_repo: self.object_repo.root().to_owned(),\n                cause: io::Error::new(io::ErrorKind::NotFound, format!(\"missing object {}\", path.display())),\n            });\n        }\n\n        Ok(Some(path))\n    }\n\n    async fn try_hardlink(&self, hash: blake3::Hash) -> Result<bool> {\n        let Some(parent) = self.parent_repo.as_ref() else {\n            return Ok(false);\n        };\n\n        let object_repo = Arc::clone(&self.object_repo);\n        let parent_repo = Arc::clone(parent);\n        if !self.dry_run {\n            spawn_blocking(move || object_repo.try_hardlink_from(&parent_repo, hash.as_bytes()))\n                .await\n                .unwrap()\n                .map_err(Into::into)","sourceCodeStart":547,"sourceCodeEnd":583,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/snapshot/src/remote.rs#L547-L583","documentation":"object_file_path in the snapshot remote code decides where each object (blob/page) goes. If the object already exists locally or can be hardlinked from a parent repo, it is reused; otherwise, in dry-run mode nothing can be fetched, so the missing object is reported as SnapshotError::ReadObject with a NotFound cause naming the expected path. Non-dry-run returns the path so the object is downloaded instead.","triggerScenarios":"Running a snapshot fetch/clone/verify with dry-run enabled against an object repo that is missing objects - a partial restore, objects removed by retention/GC, or a parent repo that lacks the requested object.","commonSituations":"Planning/sizing runs (--dry-run) after objects were garbage-collected; verifying a supposedly complete object store; a misconfigured parent repo path.","solutions":["Run the operation without dry-run so missing objects are actually fetched.","Restore or re-download the object repository from the upstream snapshot source.","If objects were intentionally pruned, fetch the missing range from a peer or full replica before dry-running."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::path::Path;\n\n// Before a dry-run, confirm every object you expect to verify is present:\nfn all_objects_present(repo_root: &Path, needed_hashes: &[blake3::Hash]) -> bool {\n    needed_hashes.iter().all(|h| repo_root.join(format!(\"objects/{}\", h)).exists())\n}","typeGuard":null,"tryCatchPattern":"match dry_run_result {\n    Err(SnapshotError::ReadObject { cause: ref e, .. }) if e.kind() == std::io::ErrorKind::NotFound => {\n        // Dry-run cannot fetch: run the operation for real so missing objects are\n        // downloaded, or restore the object repo from the upstream source.\n    }\n    r => r,\n}","preventionTips":["Run the real (fetching) operation before dry-running verification passes.","Keep object repos complete: pair them with the snapshots that reference them.","If retention prunes objects, fetch missing ranges from a full replica before planning runs."],"tags":["snapshot","objects","dry-run","rust","spacetimedb"],"backgroundTag":"missing-object-file","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}