diff --git a/hive-matrix-mcp/src/client.rs b/hive-matrix-mcp/src/client.rs index 9230158e..5303341e 100644 --- a/hive-matrix-mcp/src/client.rs +++ b/hive-matrix-mcp/src/client.rs @@ -208,12 +208,18 @@ pub async fn sync_avatar(client: &Client, state_dir: &Path, account: &str) { return; } }; - // A non-crypto content hash is enough to answer "did the icon change?". + // FNV-1a content hash — enough to answer "did the icon change?" and + // deterministic across Rust/std versions (unlike `DefaultHasher`, whose + // output may change between toolchains, spuriously mismatching the + // persisted hash and re-uploading the same avatar). No crypto strength + // needed here, so no dependency on a hashing crate. let hash = { - use std::hash::{Hash, Hasher}; - let mut h = std::collections::hash_map::DefaultHasher::new(); - bytes.hash(&mut h); - format!("{:016x}", h.finish()) + let mut h: u64 = 0xcbf2_9ce4_8422_2325; + for &b in &bytes { + h ^= u64::from(b); + h = h.wrapping_mul(0x0000_0100_0000_01b3); + } + format!("{h:016x}") }; let hash_file = state_dir.join("avatar-icon-hash"); if let Ok(prev) = fs::read_to_string(&hash_file).await