Skip to content

Errors and failure modes

Every error this package can produce, by the message you will actually see. Errors are created and wrapped with github.com/cockroachdb/errors, so a wrapped cause is joined with : and the whole chain works with errors.Is / errors.As.

The last two sections cover the failures that are not errors: the calls that panic, and the calls that quietly do nothing.

cannot derive columns from nil; use WithColumns

Table(nil) or Write(nil, nil) in a tabular format, with no WithColumns. There is no type to read table: tags from, so no columns can be derived.

Pass a typed nil slice ([]Release(nil)) instead of an untyped nil, or supply WithColumns. A typed empty slice renders the header row and no data rows.

Table requires a slice of structs or explicit WithColumns

The element type is not a struct — most often []map[string]any. Maps carry no struct tags, so their columns cannot be inferred:

_ = r.Table(rows, output.WithColumns(
    output.Column{Header: "Name", Field: "name"},
))

Slices of pointers ([]*Release) and structs with embedded (promoted) fields are fine; both are unwrapped before the tags are read.

no table tags found on struct

The element type is a struct, but no exported field carries a table: tag, or every tag is -. Tag at least one field, or pass WithColumns to render a type you do not own.

rows must be a slice

Columns resolved, but the value passed is not a slice — commonly a single struct where a one-element slice was meant. Table and the tabular branch of Write always render a collection.

_ = r.Table([]Release{rel})   // not r.Table(rel)

unknown sort column: "..."

WithSortBy names a column that is not in the resolved column set.

The value is matched against the column's Header, not the Go field name and not the struct tag as a whole. For Name string `table:"NAME,sortable"` the sort key is "NAME". Matching is exact and case-sensitive.

This error is returned for text, markdown, csv and tsv. In json and yaml the sort is never applied, so the same mistake passes silently — see behaviour by format.

column "..." is not sortable

The column exists but is not marked sortable. Add sortable to the struct tag (table:"NAME,sortable") or set Sortable: true on the Column.

The flag is deliberate: sorting is a promise about a column's values, and a column of free-form prose or a formatted composite is usually not one you want users ordering by.

encode json: ...

json.Encoder.Encode failed — either the value is not marshalable (a channel, a function, a cyclic structure, a MarshalJSON that returned an error) or the destination writer failed. Returned by Write, Table and Emit in JSON mode.

encode JSON response: encode json: ...

The same failure reached through Emit, which adds its own wrap. The double prefix is expected; the useful part is the tail.

encode yaml: ...

yaml.Encoder.Encode failed, or the destination writer did. Note that a value YAML cannot represent does not produce this error — it panics. See what panics.

write markdown: ...

Render could not write to the destination. The Markdown-to-ANSI conversion itself never fails: if glamour cannot build a renderer or cannot render the input, RenderMarkdown returns the original content unchanged.

write header: ... / write row: ... / flush delimited output: ...

The CSV or TSV writer failed. In practice all three mean the destination writer returned an error, since the encoder itself does not reject any string content.

unexpected model type from spinner

Internal: the Bubble Tea program returned a model that is not the spinner's own. It should not be reachable. If you see it, the module has a bug worth reporting.

Note that a Bubble Tea startup failure does not produce an error — Spin falls back to the plain non-interactive path and runs your function anyway.

context canceled from Spin

Spin and SpinWithResult return context.Canceled when the user presses Ctrl-C while the spinner is on screen. The in-flight function's context is cancelled first, so the work stops rather than continuing behind a dismissed spinner, and the caller sees a failure rather than a false success with a zero value.

Test it with errors.Is(err, context.Canceled). Cancellation from your own context propagates the same way, so distinguish the two by checking your own context if that matters.

What panics instead of returning an error

Three inputs take down the process rather than returning an error. All are programming mistakes rather than user input, but none is rejected up front:

Input Panic
An unexported struct field carrying a table: tag reflect.Value.Interface: cannot return value obtained from unexported field or method
A map row whose key type is not string ([]map[int]string) reflect.Value.MapIndex: value of type string is not assignable to type int
A value YAML cannot represent (channel, function) in yaml format cannot marshal type: chan int

Tag only exported fields, key row maps by string, and keep marshal-safe types in the payload.

What fails silently

Situation What happens
ParseFormat given an unknown or empty string returns (FormatText, false) — no error; the bool is the only signal
A Format value outside Formats() Write and Table fall through to text rendering
Column.Field names a field or key that does not exist the cell renders as an empty string
Emit / EmitError in a non-JSON format returns nil, writes nothing
Render in JSON format returns nil, writes nothing
WithSortBy with an invalid column, in json or yaml no error, no sort
WithNoHeader in csv or tsv header row written anyway
A Formatter applied to a numeric column the sort compares formatted strings, so "100 B" orders before "9 B"

The last one is the one that bites in production. Sorting runs after extraction and formatting, on the rendered cell text. The comparison tries strconv.ParseFloat on both operands first and falls back to a string compare, so a formatter that appends a unit turns a numeric sort into a lexical one. Sort on a raw column and format a different one if you need both.