Troubleshooting
Common AI Transport problems and how to fix them. Each entry follows the same shape: the symptom you see, what to check to confirm the cause, and the fix to apply.
Entries are ordered roughly by how often they cause support tickets, most common first.
Channel rule not configured for AI Transport
Symptom: Users see an empty assistant message, or a message containing only the first token. The server errors on subsequent publishes. The initial message.create succeeds, but the message.append operations that AI Transport uses to stream tokens fail with error code 93002 (Can only update/delete/append messages on channels with mutableMessages enabled) because no channel rule permitting appends matches the channel. This is the single most common AI Transport failure.
Confirm:
- Check the server logs for error
93002after the first token. The message readsCan only update/delete/append messages on channels with mutableMessages enabled. - Open the Ably dashboard for your app's settings.
- Find the channels your conversations live on (for example channel names like
conversations:abc, covered by the match expressionconversations:*). - Check whether Message annotations, updates, deletes, and appends is enabled on a channel rule matching those channels.
Fix:
- Enable the Message annotations, updates, deletes, and appends rule on a match expression covering those channels, using the dashboard, Control API, or CLI.
- Note that enabling this rule causes matching messages to be persisted regardless of whether persistence is enabled separately.
Capability or token scope mismatch
Symptom: The token authenticates, but specific operations fail. There are two common shapes:
- Channel pattern mismatch: channel attachment fails, or a publish is rejected. The capability covers
conversations:*but your app useschat:abc. - Missing operation: the connection works but a specific operation does not. Clients cannot send the user's message (missing
publish). A replay after a reload or a discontinuity fails, and only live messages arrive (missinghistory). Agent presence never updates (missingpresence).
Confirm:
- Decode the JWT returned by your auth endpoint and inspect the
x-ably-capabilityclaim. It is a JSON-encoded map of channel patterns to permitted operations. - Compare each operation your application performs against the capability for the relevant channel name.
Fix:
- Make the channel pattern in the capability cover the channel names your application uses. Patterns are case-sensitive; wildcards like
conversations:*only cover channels with that exact prefix. - Grant every operation the application needs:
subscribeandpublishfor messages,historyfor loading past conversation,presencefor agent presence. Check the full list of capability operations against the AI Transport capability shape.
History disappears
Symptom: Past conversation messages are not available when the user expects them. Two scenarios trigger the same root cause:
- The user opens the app the next day, tries to scroll back, and older messages are gone.
- The user is offline longer than the retention period. On reconnect the client subscribes again with a replay, but the replay resolves with
found: falseand the missed messages do not arrive.
In both cases the cause is the same: no channel rule matching the channel is configured to persist messages long enough for the use case.
Confirm:
- Check your package's retention period. The appends rule persists messages for that period, and older messages are no longer on the channel.
- Confirm whether your application is meant to read history from Ably alone, or to hydrate from an external store.
Fix:
- Check that your retention period covers your expected scroll-back window.
- For conversations that need to be retained for longer than the channel allows, persist completed responses to your own store with their serials, and replay the channel from the stored serial when the client loads.
A second subscription misses messages
Symptom: A component that subscribes with history after another subscription is already running shows the replayed history and the live messages, but some messages from in between are missing.
Confirm:
- Check whether the transport's channel was already attached when the subscription started. The replay reads history from the point where the channel attached, so only the first subscription with
historyon a transport has no gap.
Fix:
- Subscribe with
historyonce per transport, and share that subscription's deliveries between the components that need them. - To replay again, for example after a discontinuity, unsubscribe and subscribe again from the serial of the last message you received, as reconnection and recovery describes.
Duplicate or missing text after a reload
Symptom: After a reload, a response appears twice, or the start of a response is missing.
Confirm:
- Check which serial the page replays from. A response appears twice when your store holds it and the replay starts from a serial before it. Text is missing when the replay starts from a serial after messages your store does not hold.
- Check that the serial your store saves is the one
pipe()resolved with for that response, and that it is saved in the same write as the response.
Fix:
- Store the serial
pipe()orsend()returns together with the messages it belongs to, and replay withfromSerialfrom that serial. - Merge messages by id, so a message that both your store and the replay hold appears once.
Message too large to publish
Symptom: A publish fails with error code 40009 (maximum message length exceeded). Inside pipe() this surfaces on the agent as PipeFailed (104008), with the Ably error as its cause. Tool outputs or model responses that contain large payloads do not reach the channel.
Confirm:
- Identify the message that failed. Tool results that include binary data, large embeddings, or full document bodies are the usual culprits.
- Check your Ably package's message size limit. The cap is 64 KiB on Free and Standard, 256 KiB on Pro and Enterprise.
Fix:
- Stream large tool results across multiple events instead of publishing them in one message.
- Persist large payloads to an external store and send only a reference (URL or ID) over the channel.
Two devices share a clientId
Symptom: Behaviour that depends on which device published a message misbehaves across two devices for the same user. Both devices' messages carry the same clientId, and presence shows the user appearing and disappearing as both devices update their state.
Confirm:
- Inspect the JWT each device receives. If both tokens have the same
x-ably-clientId, they are indistinguishable to the Ably service. - Check whether your token-issuing logic generates a unique identifier per device (typically
userId + deviceId), or only usesuserId.
Fix:
- Assign a unique
clientIdper device for any case where ownership matters. A common pattern is<userId>:<deviceId>so the user remains identifiable while devices remain distinguishable. - Check your ownership assumptions against multi-device and fan-out.
Reconnect loop
Symptom: The client connects, drops, reconnects, drops again. The cycle repeats every few seconds. Channel state never stabilises.
Confirm:
- Inspect the Ably connection state on the client (
realtimeClient.connection.state). A reconnect loop cyclesconnecting→connected→disconnectedrapidly. - If each
disconnectedevent carries a token error, your auth endpoint is returning tokens that Ably is rejecting on every reconnect.
Fix:
- Verify the auth endpoint signs tokens with the correct key for the right environment (a dev key against a prod app is a common cause).
- Lengthen token TTL if it is set very short. The SDK refreshes ahead of expiry; very short lifetimes fight the refresh.
- If the token is rejected for capability rather than signing, see capability or token scope mismatch.
Agent process crashes mid-stream
Symptom: A streamed response stops part-way through and never finishes. No more text arrives, and no finish event or other end of the response follows.
Confirm:
- Check server logs for the crashed process around the timestamp of the affected turn. An exception or an OOM kill typically appears there.
- If you cannot see the crash directly, check infrastructure signals: serverless function timeouts, container OOM kills, and process restarts are common causes.
- If the process did not crash, check whether
pipe()rejected. A pipe that fails rejects withPipeFailed, and the route has to tell clients the response ended, for example with anabortevent.
Fix:
- Add structured error logging around the streaming path so the cause is visible. Common causes are model provider errors, OOM in long tool calls, and serverless function timeouts.
- Decide on the application response: surface a retry control to the user, or auto-retry from the application layer. AI Transport does not retry the LLM call automatically.
Publish fails in suspended state
Symptom: A publish from the client returns an error. The connection has been disconnected for more than around two minutes and has moved to suspended state.
Confirm:
- Inspect the connection state at the moment of the failed publish. A
suspendedconnection has lost message continuity, and there is no live connection to the Ably platform.send()rejects withSessionSendFailed(104005), with the Ably failure as itscause.
Fix:
- Check connection state before publishing user-driven actions, and queue them locally while the connection is not
connected. - Flush the queue once the connection returns to
connected.ably-jsdoes not buffer publishes through asuspendedstate because continuity has already been lost.
Messages arrive with no event
Symptom: Something publishes to the conversation channel, and your handler receives a delivery with event set to undefined. The message is visible in the Ably dashboard.
Confirm:
- Inspect the message's
extrasin the dashboard. A codec built withdefineCodecreads the event type fromextras.ai.type. A message without it is not the codec's, so the codec produces no event for it. - Check the transport's
errorevent. A message the codec recognises but fails to decode is reported there asSessionMessageProcessingFailed, and still delivered with no event. - Check what published it. A plain
channel.publish()from application code, or a Pub/Sub SDK client sharing the channel, produces messages in this shape.
Fix:
- Publish conversation content through the transport's
send()orpipe(), so the codec encodes it. - Keep application-level traffic off the conversation channel, or ignore deliveries with no event in your handler. Every message on the channel is delivered, so your handler can still read another publisher's message from
delivery.message.
When to contact support
If you have worked through the entries on this page and the symptom does not match, or the fix did not work, capture:
- The channel name.
- The date and time of the affected operation.
- The
clientIdof the affected server agent or client device. - The first error log entry on either side.
Open a support ticket with these. The Ably side of the system is observable to support; the application side needs the IDs to correlate.