{"record":{"id":"34337d93b82ed58b","repo":"rustfs/rustfs","slug":"invalidinput-34337d","errorCode":"InvalidInput","errorMessage":"an exact tier delete requires a remote version ID","messagePattern":"an exact tier delete requires a remote version ID","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/ecstore/src/services/tier/warm_backend.rs","lineNumber":121,"sourceCode":"    /// family uses the transition client's declared-length request plus\n    /// Content-MD5 for multipart parts, while GCS materializes the body before\n    /// awaiting its buffered write response. Test backends may deliberately\n    /// violate this contract to exercise transition compensation.\n    async fn put(&self, object: &str, r: ReaderImpl, length: i64) -> Result<String, std::io::Error>;\n    /// The same completion contract as [`WarmBackend::put`] applies when\n    /// metadata is attached.\n    async fn put_with_meta(\n        &self,\n        object: &str,\n        r: ReaderImpl,\n        length: i64,\n        meta: HashMap<String, String>,\n    ) -> Result<String, std::io::Error>;\n    async fn get(&self, object: &str, rv: &str, opts: WarmBackendGetOpts) -> Result<ReadCloser, std::io::Error>;\n    async fn remove(&self, object: &str, rv: &str) -> Result<(), std::io::Error>;\n    async fn remove_exact(&self, object: &str, rv: &str) -> Result<(), std::io::Error> {\n        if rv.is_empty() {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidInput,\n                \"an exact tier delete requires a remote version ID\",\n            ));\n        }\n        self.remove(object, rv).await\n    }\n    async fn probe_transition_candidate(&self, _object: &str) -> Result<TransitionCandidateProbe, std::io::Error> {\n        Ok(TransitionCandidateProbe::Unsupported)\n    }\n    async fn in_use(&self) -> Result<bool, std::io::Error>;\n}\n\nfn parse_http_timestamp(value: &str) -> Option<OffsetDateTime> {\n    OffsetDateTime::parse(value, &Rfc3339)\n        .or_else(|_| OffsetDateTime::parse(value, &Rfc2822))\n        .ok()\n}\n","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/ecstore/src/services/tier/warm_backend.rs#L103-L139","documentation":"Default implementation of WarmBackend::remove_exact in rustfs-ecstore. Backends that cannot pin an exact remote version fall back to this default, which refuses to delete without a remote version ID: with rv empty there is no way to guarantee the delete targets the specific transitioned instance, so it fails fast with InvalidInput instead of deleting the wrong or latest version.","triggerScenarios":"ILM tier cleanup or transition rollback calls remove_exact(object, \"\") — the object metadata carries no remote version ID (tier bucket unversioned or the transition response omitted a version) and the backend did not override remove_exact with version-aware semantics.","commonSituations":"Tier bucket created without versioning enabled so transitions record no version ID; mixed fleet where an older node wrote transition metadata without remote-version state; provider returning no version header on PUT; manual metadata surgery removed the remote-version field.","solutions":["Enable versioning on the remote tier bucket, then re-transition affected objects so their metadata records a remote version ID","Use a provider/backend that returns version IDs on upload (S3-family tiers override remove_exact with exact semantics)","Audit transitioned objects whose metadata lacks the remote-version field and re-run their transitions","Do not hand-edit xl.meta transition fields; let the transition path populate the remote version"],"exampleFix":"# before: tier bucket unversioned\naws s3api create-bucket --bucket tier-bucket\n# after: versioned tier bucket records a remote version ID per transition\naws s3api put-bucket-versioning --bucket tier-bucket --versioning-configuration Status=Enabled","handlingStrategy":"validation","validationCode":"// Skip exact-delete when no remote version was recorded\nif remote_version_id.is_empty() {\n    // tier bucket unversioned or transition predated version tracking;\n    // re-transition instead of calling remove_exact\n}","typeGuard":null,"tryCatchPattern":"match backend.remove_exact(object, rv).await {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && rv.is_empty() => {\n        // missing remote version: enable versioning and re-transition\n    }\n    other => other?,\n}","preventionTips":["Enable bucket versioning on tier targets at creation time","Alert when transitions complete without a returned version ID","Never strip remote-version fields from transition metadata"],"tags":["tiering","lifecycle","versioning","remote-storage","delete"],"backgroundTag":"missing-remote-version-id","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}