{"record":{"id":"8a5abdb9e2d3dbd5","repo":"clockworklabs/SpacetimeDB","slug":"stored-reset-program-changed","errorCode":null,"errorMessage":"stored reset program changed","messagePattern":"stored reset program changed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/standalone/src/lib.rs","lineNumber":484,"sourceCode":"                Program::from_bytes(host_type.into(), &bytes[..])\n            }\n            None => {\n                // A reset without an artifact retains the currently committed\n                // module, not the initial program or its old environment values.\n                let module = self.leader(database.id).await?.module().await?;\n                module\n                    .relational_db()\n                    .program()?\n                    .context(\"database is not initialized\")?\n            }\n        };\n        database.host_type = HostType::from(program.kind);\n        database.initial_program = program.hash;\n        self.host_controller\n            .check_module_validity_with_environment(database.clone(), program.clone(), environment.clone())\n            .await?;\n        let stored = self.program_store.put(&program.bytes).await?;\n        anyhow::ensure!(stored == program.hash, \"stored reset program changed\");\n        let previous_replicas = self.control_db.get_replicas_by_database(database.id)?;\n        for replica in &previous_replicas {\n            self.on_delete_replica(replica.id).await?;\n        }\n        let (_, replica) = self.control_db.upsert_database_with_environment(\n            database,\n            Some(&previous),\n            environment,\n            &previous_replicas,\n        )?;\n        self.on_insert_replica(&replica).await?;\n        Ok(())\n    }\n\n    async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> anyhow::Result<()> {\n        let balance = self\n            .control_db\n            .get_energy_balance(identity)?","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/eddf9f5014579a50d4b67630e28b6e15cad9c4af/crates/standalone/src/lib.rs#L466-L502","documentation":"After persisting the new program bytes with `program_store.put`, `reset_database` asserts the store's returned content hash equals the hash computed from the in-memory program. A mismatch means the stored bytes differ from what was validated with `check_module_validity_with_environment`, i.e. the program storage backend returned a different hash than expected — an internal integrity violation, so the reset is aborted before swapping replicas.","triggerScenarios":"`program_store.put(&program.bytes)` returns a hash that differs from the precomputed `program.hash`, e.g. a buggy/misconfigured content-addressed store, hash-algorithm mismatch between the program builder and the store, or corrupted/mutated program bytes between hashing and storage.","commonSituations":"Custom or sharded program stores with a hashing bug; upgrading SpacetimeDB so the store's hashing no longer matches; running with a storage backend (S3/local) that altered or truncated the bytes; concurrency bugs where bytes were modified during an async put.","solutions":["Verify the program store's hashing algorithm and serialization match how `program.hash` was computed (same hash function, same byte slice).","Recompute the hash from the exact bytes passed to `put` and compare before calling put to isolate where the mismatch originates.","Check for concurrent mutation of `program.bytes` between hashing, validity checking, and storage; clone/own the bytes early.","Reproduce against the default program store; if only a custom store fails, fix or roll back the custom store."],"exampleFix":"// before\nlet stored = self.program_store.put(&program.bytes).await?;\nanyhow::ensure!(stored == program.hash, \"stored reset program changed\");\n// after (verify before persisting)\nlet recomputed = Hash::from_bytes(&Sha256::digest(&program.bytes));\nanyhow::ensure!(recomputed == program.hash, \"program hash mismatch before storing\");\nlet stored = self.program_store.put(&program.bytes).await?;\nanyhow::ensure!(stored == program.hash, \"stored reset program changed\");","handlingStrategy":"validation","validationCode":"use sha2::{Digest, Sha256};\nlet recomputed: [u8; 32] = Sha256::digest(&program.bytes).into();\nassert_eq!(&recomputed, program.hash.as_slice(), \"program hash drifted before storage\");","typeGuard":"fn hash_matches(program: &Program, stored: &Hash) -> bool { stored == &program.hash }","tryCatchPattern":null,"preventionTips":["Compute the hash from the exact byte slice you store; never mutate bytes after hashing","Keep the program store's hashing algorithm in lockstep with the program builder","Add an integration test hashing round-trips through the program store","Own/clone program bytes early to avoid concurrent mutation"],"tags":["rust","integrity","hash-mismatch","storage"],"backgroundTag":"checksum-mismatch","analyzedSha":"eddf9f5014579a50d4b67630e28b6e15cad9c4af","analyzedAt":"2026-09-20T12:15:59.611Z","contentChangedAt":"2026-09-20T12:15:59.611Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}