I'm not sure I'm saying anything you don't know, but I've seen a few ways to handle it: compare to known instances when they are exported and appear in the godoc, like io.EOF or os.ErrNotExist; using type assertions or switches when the docs promise an error of a certain type, like *os.PathError or *os.LinkError; or giving up and looking at messages. The first should be clear enough and you've done the third in your question; checking for type could look like:
if pathErr, ok := err.(*os.PathError); ok {
log.Fatal("Bad path " + pathErr.Path + ", giving up")
} else if err != nil {
return err
}
I think this would be rare, but if you had several potential types involved, you could conceivably use a type switch:
switch err.(type) {
case *os.PathError, *os.LinkError:
log.Fatal("oof")
case nil:
// nothing; prevents default
default:
log.Fatal("huh")
}
(The choice of error and behavior here is a bit silly--I'm just trying to put up a template with the syntax.)
The hard part you allude to in your question: it may not be clear what guarantees you have about what errors will be returned. I know that, for example, the 1.5 release notes say that they're standardizing more on net.OpError for net errors than before, but that's all I know. Spelunking in the source of the package you're calling, or asking people questions, may be in order if there are important cases you think are unclear.
erroris fragile at best and not a good idea. I wrote an answer to another question that goes over the ways a package author can make errors that are testable by consumers. If you need to test for or handle errors that don't use one of those easy methods then you've found a flaw in that package and should report it as an issue. I'd go so far as fixing it in a fork before I'd muck around with the error string.