The code closest to the source of the error is responsible for providing as much detail about the error and if possible classifying it if such is needed.
Motivation: The further you get from the error the harder this becomes.
Code further up in the call stack is responsible for deciding on how to act on the error given the current context.
Motivation: Called code has no idea of the context.
A DB error is thrown. The code closest to the error classifies it as a socket time out. This is bubbled up in the call stack which then decides to not retry because a user has decided that the query is no longer relevant.
These principles apply not only within an in-memory call stack but also across e.g. micro-service boundaries.
Do you disagree? I would love your input.