Errors and sentinels¶
Every exported error value in the module, what raises it, and what fixes it.
All of them are matched with the standard library's errors.Is; none requires
phpboyscout/go/errors at the call site, even though the module builds its
errors with it.
Construction errors¶
Raised by chat.New, chat.NewFallback* and chat.NewFileStore, before any
request is made.
| Sentinel | Raised when | What to do |
|---|---|---|
chat.ErrUnableToConstruct |
construction failed outright and no client was returned | fix the named cause; this is the one check every caller should make, because it separates "no client" from "a client missing a setting" |
chat.ErrInvalidBaseURL |
Config.BaseURL fails any endpoint rule (too long, control characters, unparseable, carries userinfo, not https, no host, or a placeholder host) or is empty for ProviderOpenAICompatible |
correct the URL; put credentials in Config.Token, never in the URL |
chat.ErrInvalidSnapshotID |
a snapshot identifier reaching FileStore.Save, Load or Delete is not a canonical lowercase-hex UUID, or resolves outside the store directory |
validate at your own boundary with chat.ValidateSnapshotID; snapshots from chat.NewSnapshot always pass |
errors.Is(err, chat.ErrUnableToConstruct) is true whenever chat.New returned
a nil client, including for every fatal cause listed on this page. The
aggregate makes the sentinel a member so no type assertion is needed.
Request-time errors¶
Raised by a provider during Chat, Ask or StreamChat. Each wraps the
vendor's own error, which stays reachable by unwrapping.
| Sentinel | Raised when | What to do |
|---|---|---|
chat.ErrThrottled |
the provider rate-limited the request and its own retries were exhausted | back off, or configure a fallback composite; do not read the vendor's wording, which is often misleading (Gemini reports a per-minute burst limit as a quota and billing problem) |
chat.ErrModelRejectedParameter |
the selected model refused a generation parameter that its provider structurally supports | change Config.Model, or drop the parameter and use Config.Effort, which every provider accepts |
chat.ErrMediaRejected |
an attachment is empty, over 20 MiB, of a type the sniffer cannot identify or the allowlist excludes, or declares a MIME family contradicting its bytes | send a supported format, or downscale; the sniffed type is authoritative and a declared type can never override it |
chat.ErrMediaUnsupported |
the selected provider accepts no media at all (claude-local), or not this attachment's type |
choose a provider that accepts the type (the table is in Defaults and limits) |
chat.ErrCommandUnavailable |
a slash command needs a contract this client does not implement: /clear without PersistentChatClient, or /compact and /undo without TranscriptEditor |
upgrade the provider module, or drop the command from your CommandSet; /help lists it either way, so a user is told rather than left guessing |
chat.ErrTranscriptMoved |
another goroutine changed the conversation while a policy was being applied, so the edit was computed against turns that no longer exist | retry; it is not a failure. The client cannot hold its lock across a policy that calls the model, so this window is unavoidable, and applying the edit anyway would silently drop the turns that arrived |
chat.ErrStatelessRestore |
Restore is called on a client built with Config.Stateless |
restore into a conversational client; a stateless client retains no conversation for a snapshot to land in. Save still works and returns a snapshot with no messages |
Media errors are raised before any network call, because the attachment is validated at the boundary, so a rejected attachment costs nothing.
The ConfigErrors aggregate¶
chat.New returns problems as one *chat.ConfigErrors rather than the first
one it hit, so a caller with three mistakes learns all three from one call.
| Member | Returns | Use it to |
|---|---|---|
Error() string |
one line naming every problem, prefixed by unable to construct client when fatal and configuration could not be fully applied when not |
log it |
Unwrap() []error |
the individual problems, plus ErrUnableToConstruct when fatal |
let errors.Is and errors.As reach every member |
Dropped() []DroppedSetting |
the settings that were not applied, each with Fields, Capability and Reason |
decide whether the shortfall matters to this job |
Fatal() bool |
whether construction failed outright | the cheap equivalent of errors.Is(err, ErrUnableToConstruct) when you already hold the type |
var cfgErr *chat.ConfigErrors
if errors.As(err, &cfgErr) {
for _, d := range cfgErr.Dropped() {
log.Info("not applied", "fields", d.Fields, "capability", d.Capability, "why", d.Reason)
}
}
Reading hints¶
Errors from this module carry hints, the half that says what to do rather than
what is wrong. Read them with the standard readers from
phpboyscout/go/errors:
import "gitlab.com/phpboyscout/go/errors"
for _, h := range errors.GetAllHints(err) {
log.Info("hint", "advice", h)
}
Both GetAllHints and FlattenHints descend into an aggregate, so they work on
anything chat.New returns.
chat.Hints was removed in v0.9.0
It existed because cockroachdb/errors walked single unwraps only, so hints
below an aggregate vanished silently. go/errors has one chain traversal
that descends into a multi-error, which removed the reason for the helper
rather than fixing it. Consumers reading hints should move to the readers
above.
Tagging a provider error: chat.MarkAs¶
Provider modules tag their vendor errors with a sentinel using
chat.MarkAs(err, chat.ErrThrottled). Both of these then hold under the
standard library:
A nil error returns nil, so a call site can wrap unconditionally.
The helper exists because of a trap the module walked into once: annotation
libraries have historically offered a Mark whose markers only their own Is
recognises, so a consumer writing the idiomatic standard-library check gets
false. MarkAs wraps with %w: %w instead, which the standard library
understands. phpboyscout/go/errors offers no Mark, so the trap is not
reachable today. The helper stays because the guarantee it makes is the one
callers depend on.
Failures that carry no sentinel¶
These are returned as plain messages. There is nothing to match on beyond the class the aggregate gives them, so treat the text as diagnostic, not as an API.
| Message | Raised when | What it usually means |
|---|---|---|
unsupported provider: <name> |
no factory is registered under that name | the provider module's blank import is missing, or the name is misspelled |
claude binary not found in PATH: … |
ProviderClaudeLocal cannot find the claude CLI |
install Claude Code and authenticate it once |
Model is required for ProviderOpenAICompatible: … |
ProviderOpenAICompatible was configured without Config.Model |
name the backend's model, e.g. llama3.2 for Ollama |
Anthropic API key is required but not provided, OpenAI token is required but not provided, Gemini API key is required but not provided |
the whole credential cascade resolved to nothing | export the provider's well-known variable, or set Config.Token or Config.Credentials |
ProviderClaudeLocal does not support SetTools in this version |
tools are registered on claude-local |
use an API provider for tool calling |
provider <name> does not support Config.Stateless |
the provider module predates chat.StatelessCapable |
upgrade the provider module; do not unset the flag |
provider <name> cannot carry Config.Temperature or Config.TopP |
the provider has no sampling concept at any model (claude-local is the case) |
use Config.Effort, or change provider |
provider <name> cannot carry Config.Effort |
the provider module predates chat.EffortCapable |
upgrade the provider module |
provider <name> cannot carry Config.CacheTTL |
the provider module does not implement chat.CachingChatClient |
upgrade the provider module, or unset Config.CacheTTL |
encryption key must be 32 bytes, got <n> |
chat.WithEncryption was given the wrong key length |
generate one with chat.GenerateEncryptionKey |
fallback: at least one provider config is required |
a fallback constructor was handed an empty slice | supply at least the primary |
fallback: no provider could be constructed |
every member of a composite failed to build; marked ErrUnableToConstruct |
fix the credentials or imports the error names |
fallback refused: a tool call has executed in this conversation |
WithStrictToolContext is set and a tool ran before the failure |
accept lossy text-only failover by removing the option, or handle the error |
<Provider> reached maximum ReAct steps (<n>) without a final answer |
the tool loop ran Config.MaxSteps times and the model never produced a final text answer |
raise Config.MaxSteps, or narrow the task (the call returns no partial text) |
The rows that describe a construction failure (the unsupported provider,
the missing claude binary, the four "cannot carry" messages, and
fallback: no provider could be constructed) all satisfy
errors.Is(err, chat.ErrUnableToConstruct), so that check stays reliable even
though the specific cause has no sentinel of its own. The remaining rows do not:
SetTools on claude-local and fallback refused are returned from a call
rather than from construction, and the chat.NewFileStore key-length error and
the empty-slice fallback error are returned bare.
Related¶
- Handle a partially-applied configuration: the three outcomes of
chat.Newin code. - Configuration fields: which field produces which failure.
- What this module does not do: the failures that are deliberately not reported at all.