While hashes are the primary identifier, things also generally have names associated with them. (Unless you deliberately remove the names that is.)
Sounds oddly much like git. Git-commits are identified by hashes.
And git commits "are" (versions of) programs.
So what does Unison have that git does not?
Mostly, it heavily normalizes code, to the point where a name change will not register as a change to the program.
Yes, we have stack traces. You might see some hashes in the middle of the stack traces, but whenever we do have a name, we'll show you the name. So if you are calling into an anonymous clousure, but functions in your call stack will typically have names available