Skip to content

Troubleshooting

Start from the symptom. Each one names where Chaski records the reason, and what to change.

A notification did not go out

Open it under Deliveries in the dashboard. A channel that never sent is recorded as skipped, with the first reason that applies:

ReasonWhat to change
channel_not_configuredSave the channel’s settings on the Channels page. A channel with none is off.
no_templateGive the type content for that channel in its template, and turn the channel on there.
muted_by_preferencesThe contact muted it, or it is muted by default. Send with transactional: true only when the contact must get it.
missing_emailSend the contact’s address as recipient.email.
no_device_tokensRegister a device for the contact before the send; see Channels.

Call ready() from your app for the same view without the dashboard: it lists the channels that are on, the paused ones, and the settings that stopped working under problems. See API.

A push did not arrive

  • The subscription belongs to another key. A browser subscribed with an earlier push key can never receive this Chaski’s pushes. client.push.enable() replaces it; see Browser clients.
  • The Apple token is in the wrong environment. A development build’s token only works in sandbox. When APNs refuses one as BadDeviceToken or DeviceTokenNotForTopic, Chaski forgets the device and logs the reason to Workers Logs. Check the build’s environment and the app’s bundle ID in the APNs settings.
  • The Windows channel expired. WNS channels live 30 days, so the app registers its channel again on every launch.

The dashboard answers “Chaski needs Cloudflare Access”

Nothing is broken. The Worker’s URL is not covered by Cloudflare Access yet, so Chaski refuses to serve the dashboard to a request with no signed-in identity. Enable Access on the hostname from the Worker’s Settings › Domains & Routes, not from its own Access tab, which refuses the WebSocket the dashboard’s live updates use. Install has the steps.

A channel shows as Failing

Its saved settings stopped working after they were saved: a binding it needs left wrangler.jsonc, or CHASKI_SECRET changed and its credentials no longer open. Its deliveries fail with a config error that says the same, and the other channels carry on. Save the setting again on the Channels page. Keep CHASKI_SECRET as it is once credentials are saved under it.

Deliveries wait as held

Someone paused the channel. A pause holds every delivery of that channel, including transactional and scheduled ones, and an inline send throws channel_paused while its email waits with the rest. Resume on the Channels page asks what to do with what the pause held; kept deliveries stay under the Held tab of Deliveries. See Pause a channel.

A send fails with an unknown type

There is no template for that type, or none at all: on a fresh database, or after a deploy that forgot to load the template file, every send is refused. Create the template in the dashboard, or load your file with templates().import(). ready() counts the templates, which makes a health check notice before your users do.

Errors lose their code locally

Between two local wrangler dev sessions, a ChaskiError reaches your app as a plain Error whose message starts with ChaskiError:, so isChaskiError() is false. Deployed Workers keep the name and the code; write the check for the deployed shape, as Connect your app shows.

A local schedule stays overdue after a restart

The local Workflows engine does not wake an instance that was asleep when wrangler dev stopped, so the schedule stays pending past its time. Call schedule() again with the same idempotencyKey to restart its instance. Deployed Workflows do not have this limitation; see Schedules across restarts.

Emails carry no open pixel

The pixel loads from where your app mounts userRoutes(). Set CHASKI_PUBLIC_URL in the Chaski Worker’s vars to that absolute URL; without it, emails carry no pixel. If your app puts its routes behind a session, the pixel’s path has to pass it, since an email client loads it with no session: Connect your app shows the extra mount.

A delivery is dead

Temporary failures retry with backoff up to the queue consumer’s five attempts, then the delivery is dead. Fix the cause the last error names, then replay it from Deliveries. Keep max_retries: 5 on the consumer, as Install declares it: Chaski counts on it to tell a delivery’s last attempt.

Updated Nov 5, 2026

MIT License · chaski.sh