handleRootError dispatches a command error to the appropriate handler and returns the process exit code. Dispatch order: 1. Typed errors from errs/ (e.g. *errs.PermissionError, *errs.APIError, *errs.SecurityPolicyError, *errs.AuthenticationError, *errs.ConfigError): render via the typed envelope wr
(f *cmdutil.Factory, err error)
| 245 | // matching the explicit flag/subcommand guards. Flag parse errors are |
| 246 | // already typed upstream by the root FlagErrorFunc. |
| 247 | func handleRootError(f *cmdutil.Factory, err error) int { |
| 248 | errOut := f.IOStreams.ErrOut |
| 249 | |
| 250 | // When the typed error is a need_user_authorization signal, fold in the |
| 251 | // current command's declared scopes as a Hint so the user/AI sees the |
| 252 | // concrete scope(s) to re-auth with. The hint is computed on the fly from |
| 253 | // local shortcut/service metadata — it never depends on server state. |
| 254 | if !errs.IsRaw(err) { |
| 255 | applyNeedAuthorizationHint(f, err) |
| 256 | } |
| 257 | |
| 258 | // Staged dispatch: capture the typed exit code BEFORE attempting the |
| 259 | // envelope write. WriteTypedErrorEnvelope is best-effort on the wire |
| 260 | // (partial-write still returns true) so the exit code we read here is |
| 261 | // preserved even if stderr is torn — torn stderr must not downgrade |
| 262 | // typed exits 3/4/6/10 to the plain "Error:" path with exit 1. |
| 263 | // WriteTypedErrorEnvelope still returns false when err carries no |
| 264 | // Problem; in that case we fall through to the signal / plain-text paths. |
| 265 | typedExit := output.ExitCodeOf(err) |
| 266 | if output.WriteTypedErrorEnvelope(errOut, err, string(f.ResolvedIdentity)) { |
| 267 | return typedExit |
| 268 | } |
| 269 | |
| 270 | // Partial-failure (batch / multi-status): the ok:false result envelope is |
| 271 | // already on stdout; set the exit code and write nothing to stderr. |
| 272 | var pfErr *output.PartialFailureError |
| 273 | if errors.As(err, &pfErr) { |
| 274 | return pfErr.Code |
| 275 | } |
| 276 | |
| 277 | // Silent-exit signal (e.g. `auth check` predicate, or `update --json`): |
| 278 | // stdout already carries the result; honor the requested exit code and |
| 279 | // write nothing to stderr. |
| 280 | var bareErr *output.BareError |
| 281 | if errors.As(err, &bareErr) { |
| 282 | return bareErr.Code |
| 283 | } |
| 284 | |
| 285 | // Errors reaching here are untyped: every RunE returns a typed errs.* error |
| 286 | // and flag-parse errors are typed by the root FlagErrorFunc. The remainder |
| 287 | // is either a cobra usage mistake (missing required flag, unknown command, |
| 288 | // wrong arg count), which cobra surfaces as a plain error identified by its |
| 289 | // stable text — the same external contract unknownFlagName relies on — or an |
| 290 | // untyped error that leaked past the typed boundary. Classify the former as |
| 291 | // invalid_argument (exit 2, like the explicit guards); treat the latter as an |
| 292 | // internal fault (exit 5) rather than blaming the user's input. The message |
| 293 | // is preserved either way, and the typed envelope still carries any pending |
| 294 | // deprecation notice. |
| 295 | var fallback error |
| 296 | if isCobraUsageError(err) { |
| 297 | fallback = errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err.Error()) |
| 298 | } else { |
| 299 | fallback = errs.NewInternalError(errs.SubtypeUnknown, "%s", err.Error()).WithCause(err) |
| 300 | } |
| 301 | output.WriteTypedErrorEnvelope(errOut, fallback, string(f.ResolvedIdentity)) |
| 302 | return output.ExitCodeOf(fallback) |
| 303 | } |
| 304 |