Errors and Troubleshooting
Check operation errors, handle asynchronous server errors, and diagnose common SDK problems.
Before you start: follow Connection and lifecycle. Register an error listener once per channel. Also check the errors that your calls return.
Every error that the SDK detects is a *celeris.Error with a stable Code. Match it with errors.Is(err, celeris.ErrTimeout) and the other sentinels, never on message text. Messages name the field and the rule that failed. They never repeat your input, a credential, or server text. Errors that the server sends are a *celeris.ServerError instead.
1. Check the call you made
if err := chat.Publish(ctx, celeris.TextPayload("Hello")); errors.Is(err, celeris.ErrNotConnected) {
fmt.Println("Wait until connected before sending.")
} else if err != nil {
fmt.Println(err)
}
Check Connect, Publish, and PresenceList where you call them. A failed initial connect returns its error to the caller and does not also reach OnError. The SDK likewise reports a failed presence query to its caller.
2. Handle server errors separately
stopErrors := channel.Events().OnError(func(err error) {
serverError, ok := errors.AsType[*celeris.ServerError](err)
if !ok {
fmt.Println(err)
return
}
fmt.Println(serverError.Type, serverError.SubType, serverError.Message)
switch serverError.Type {
case celeris.PermissionDeniedError:
fmt.Println("Check the server-issued channel and segment permissions.")
case celeris.MessageSizeLimitError:
fmt.Println("Reduce the payload size.")
case celeris.RateLimitError:
// The SDK pauses and resends recent commands by itself.
fmt.Println("Rate limited; publish less often if this repeats.")
default:
fmt.Println("The server reported an error.")
}
})
// Later:
stopErrors()
A ServerError carries Type, SubType (the command it answers, or ""), and Message (the server's own text). Resource is what that command names, such as the segment. A ServerError has no Code. Match it with errors.AsType. Error types are open-ended, so retain a default branch for a newer server. A resource can be nil, a string, an int32, an int64, or a []any of these values. Check its type before you use it.
A permission denial can identify a command and a segment. It does not identify which of several otherwise identical publishes caused the error. The SDK correlates presence-query errors internally. Those errors return from PresenceList instead. Server error text is for people, not for programs. Show it as text, never as raw HTML. Server errors leave the connection open.
3. Choose the right response
| Sentinel | Response |
|---|---|
ErrConfiguration | Fix the invalid argument or credential. A retry with unchanged input does not help |
ErrNotConnected | Wait for a connection, then try again. A publish waits in the queue only while the channel is reconnecting |
ErrTimeout | Check the credential endpoint and network, the query deadline, or your context's deadline |
ErrCancelled | Your context ended, or the channel closed during the operation. Do not automatically restart canceled work |
ErrTransport | Check network and credential setup. This does not prove an authentication failure |
ErrBackpressure | Slow production. The publish queue (PublishQueueSize, 64 by default) is full, or a presence query found the writer full or paused |
ErrOperationInProgress | Do not start simultaneous connects or presence queries on one channel |
ErrDeliveryUnknown | Acceptance is uncertain. Do not blindly resend an irreversible operation |
ErrProtocol | One frame was dropped. The connection remains usable. Field and Offset locate the failure |
*ServerError | An error that the server sent. See step 2 |
The SDK recovers a panic in a listener and reports it through OnError as ErrTransport. Recover panics in goroutines that your listeners start. A nil listener registration panics at once. That is a programming error.
4. Diagnose common symptoms
| Symptom | First checks |
|---|---|
| First connect fails and nothing retries | This is expected. Call Connect again from failed |
| Socket connects but messages are absent | Channel/segment match, a a held subscription, and read permission |
| Sender sees no received copy | AllowEcho defaults to false |
Publish returns nil, then an error arrives | Expected for server refusals. A nil return is only local acceptance |
PresenceList always returns ErrTimeout | The query runs inside a listener. Call it from your own goroutine |
| Presence events miss a user | You get events only for joins and leaves while you hold presence. Query a cluster-wide listing |
| Credentials were denied but the error says Transport | The handshake status is unavailable to the SDK |
| Messages are absent after recovery | Replay is bounded. Reload authoritative application state |
Channel stops in failed after a connection loss | OnError gives the cause. Recovery used all MaximumReconnectAttempts (10 by default), or a failure other than ErrTransport or ErrTimeout stopped it. Call Connect again |
PresenceList returns ErrConfiguration | page must be at least 1, and perPage must be from 1 to 100 |
| A closed channel cannot reconnect | Obtain a new channel from the client |
See Reconnection and Recovery for which failures retry. Do not run your own retry loop while the channel is in the reconnecting state.
Next: Server-side usage, or the Go client API for the full error surface.