{"record":{"id":"fc7c558b3f71cfcf","repo":"rust-lang/cargo","slug":"registry-said-cache-valid-when-no-cache-exists","errorCode":null,"errorMessage":"registry said cache valid when no cache exists","messagePattern":"registry said cache valid when no cache exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/sources/registry/index/mod.rs","lineNumber":556,"sourceCode":"                Ok((s, v)) => {\n                    cached_summaries = Some(s);\n                    index_version = Some(v);\n                }\n                Err(e) => {\n                    tracing::debug!(\"failed to parse {lowered_name:?} cache: {e}\");\n                }\n            }\n        }\n\n        let response = load\n            .load(root, relative.as_ref(), index_version.as_deref())\n            .await?;\n\n        match response {\n            LoadResponse::CacheValid => {\n                tracing::debug!(\"fast path for registry cache of {:?}\", relative);\n                if cached_summaries.is_none() {\n                    return Err(anyhow::anyhow!(\n                        \"registry said cache valid when no cache exists\"\n                    ));\n                }\n                return Ok(cached_summaries);\n            }\n            LoadResponse::NotFound => {\n                cache_manager.invalidate(lowered_name);\n                return Ok(None);\n            }\n            LoadResponse::Data {\n                raw_data,\n                index_version,\n            } => {\n                // This is the fallback path where we actually talk to the registry backend to load\n                // information. Here we parse every single line in the index (as we need\n                // to find the versions)\n                tracing::debug!(\"slow path for {:?}\", relative);\n                let mut cache = SummariesCache::default();","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/rust-lang/cargo/blob/98a09e7e7d62850f14e5b6132101fc1edd19a16f/src/sources/registry/index/mod.rs#L538-L574","documentation":"The registry backend returned `LoadResponse::CacheValid` (meaning the client's cached `index_version` is still current), but the caller had no in-memory `cached_summaries` to return. This is an internal invariant violation: the backend should not claim a cache is valid when the index layer holds nothing to validate against.","triggerScenarios":"In `load_summaries`, after `cache_manager.get(lowered_name)` failed to parse (or returned nothing) so `cached_summaries` is `None`, yet the remote responded with `CacheValid`. Indicates a logic bug, a cache/index state desync, or a registry server incorrectly returning 304/Not-Modified for content the client never had.","commonSituations":"Corrupt local cache that failed to parse (so `cached_summaries` is None) combined with a server returning a cache-valid signal; race between cache invalidation and load; bug in a custom registry backend; partial cache after a manual `rm` of cache files but not the version tracker.","solutions":["Clear the entire registry index cache: `rm -rf ~/.cargo/registry/index/<name>` then `cargo fetch`.","If operating a private registry, ensure it returns `404`/data (not `304 Not Modified`) when the client supplies an `index_version` it never received data for.","Update Cargo — this often indicates a bug fixed in newer versions.","Report with `--verbose` logs if it reproduces on the latest Cargo."],"exampleFix":"# before: desync between version tracker and cache body\n$ cargo build\nerror: registry said cache valid when no cache exists\n\n# after: full index reset\n$ rm -rf ~/.cargo/registry/index/<name>\n$ cargo fetch","handlingStrategy":"try-catch","validationCode":"// Caller-side: only pass index_version when we actually have cached data.\nlet version_to_send = cached_summaries.as_ref().and_then(|_| index_version.as_deref());\nlet resp = load.load(root, relative.as_ref(), version_to_send).await?;","typeGuard":"fn cache_state_consistent(cached: &Option<Summaries>, resp: &LoadResponse) -> bool {\n    !matches!(resp, LoadResponse::CacheValid) || cached.is_some()\n}","tryCatchPattern":"match response {\n    LoadResponse::CacheValid if cached_summaries.is_none() => {\n        // recover: invalidate and re-load unconditionally\n        cache_manager.invalidate(lowered_name);\n        self.load_summaries(name, load).await\n    }\n    other => /* normal handling */,\n};","preventionTips":["Treat `CacheValid` without local data as a signal to re-fetch, not a fatal error.","Don't partially delete cache files; clear the whole index dir.","Keep private registry backends correct about 304 semantics.","Upgrade Cargo to pick up invariant-recovery fixes."],"tags":["cargo","registry","cache","invariant","internal"],"backgroundTag":null,"analyzedSha":"98a09e7e7d62850f14e5b6132101fc1edd19a16f","analyzedAt":"2026-08-11T17:42:36.556Z","contentChangedAt":"2026-08-11T17:42:36.556Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}