You can pass the error (or another) up the stack. The language is flexible.
If you do it that way, you seem to forego a lot of error encapsulation straight away and quickly find yourself into undefined territory.
In fact, in go those aren't even the only options, since you can pass an error object up the stack using panic/recover.
An alternative model could have error handlers registered somewhere and be invoked at the point the error occurs, either choosing to pass control to another part of the program, or resolve the issue and continue processing. That could, of course, be implemented in vanilla Go, but then everything has to agree to do that.
Some systems are able to distinguish the point where you recover from an error from the point where you report an error: e.g. a batch processing system may recover from an error by skipping an item to be processed (and perhaps storing it in a list of failed items to be inspected later) and another "catch" handler further up may decide whether and how to display a diagnostic about the issue. Of course, this logic could be embodied as a "reporting" object which is passed down to the batch processing algorithm, but hooking into the exception handling logic means that you can use the existing error-reporting infrastructure.
Common Lisp has a system of "restarts" where one can configure multiple ways for a handler to respond to errors other than simply passing it up or carrying on. For example, a batch processing system might set up restarts to allow items to be skipped, retried now, or retried after the batch finishes. The batch processor itself might have restarts for retrying the batch or rescheduling it on another node. Above the batch processing algorithm, an exception handler can look at the situation from a high level and decide what to do: e.g. it may determine that the batch has grown too big for the node and reschedule it on another one.
Until the decision is made, the stack is not unwound, so if I decide to restart an item from the batch, I just jump right back into the still-running batch processing function. This design allows the mechanism for how to actually deal with an error condition to be separated from the decision as to which mechanism to use. It's very much like breaking into a debugger, except the program can debug itself.
In Erlang, a program handles errors by keeling over, dead. Because an Erlang system is (meant to be) designed as a swarm of cooperating processes, an individual process can just die when something goes badly wrong and its compadres are expected to have registered an interest in knowing that this has happened. A common design pattern is to have a dedicated monitoring handling process which knows how to orchestrate things so that one broken process doesn't cause a cascading failure through the whole system.
In the running example, a batch processing system might spawn a process for each batch, and a monitor process might check for batches which die and then decide whether and where to restart them.
That said, Erlang still has a fairly traditional try/catch system for when that makes more sense. And errors are objects, so you can return them up the call stack as well.
There are many more mechanisms for error handling which aren't simply "handle here or pass up".
(C++-like) exceptions pass the error up the stack. Directly to the right place, and ensuring that clean-up takes place along the way without tons of repeated test-and-bail code.
Lisp-like are one better: the stack is searched for a binding to a function which handles the error, and then a new call is generated to that function. If the function returns, the search for the next possible one continues. Or the function can then look for a restart point somewhere the stack (the entire stack still being intact), and perform a dynamic non-local transfer (a big return) to that point.
Dumb error return values make it very easy for an error to disappear without being handled. Oh, the caller has a few things to do before returning your value (things such as clean up that could be an unwind block!) and fumbles the ball somehow, and ends up returning a different value: the information is altered like in the telephone game.
That's the problem: every function level between the source of the situation and the place where it is to be handled intercepts the handling effort by being involved in the control flow. When it should just be getting the heck out of the way (like cars pulling to the side when they hear an ambulance).
Bubbling up error values is an excellent example of an "Anti Pattern".