Skip to content

Error Handling

Every way a run can fail has a sentinel error in internal/releasechanges/errors.go. Sentinels are always wrapped with %w, and each maps to a stable kind string.

The wrapping rule

A call site with context to add never returns a sentinel bare, and never renders one with %v or into a freshly constructed error — either breaks both errors.Is matching and KindOf:

return fmt.Errorf("%w: %s", releasechanges.ErrTagNotFound, tag)
func KindOf(err error) string // stable kind string, or "" when no known sentinel is wrapped

The kind strings are a public contract, for a future machine-readable error output and for skills that match on them. They may be added to, never renamed.

Sentinels

SentinelKind stringRaised when
ErrRepoNotFoundrepo_not_foundThe repository does not exist or is not visible (a 404)
ErrTagNotFoundtag_not_foundThe tag (or --previous-tag) does not exist — checked before generate-notes, which would not
ErrForbiddenforbiddenGitHub answers 403, other than a rate limit; generate-notes needs Contents: write
ErrInteractiveRequiredinteractive_requiredA prompt is needed, but stdin and stdout are not terminals
ErrInvalidRepoRefinvalid_repo_ref-R is not in [HOST/]OWNER/REPO form, or --scope-issue-repo / --relative-to not in OWNER/REPO form

Adding a sentinel means adding a KindOf case, a row here, and an entry in the allSentinels test table. That test parses the package source for exported Err* variables, so an untabled sentinel fails the build rather than shipping without a kind.

How internal/github maps HTTP responses onto these is on the GitHub Client page.

Where errors are printed

Exactly once, by main, as error: <message> on stderr through output.Writer. The root command sets SilenceErrors so Cobra does not print its own copy, and SilenceUsage so a runtime failure is not buried under the usage block.