Help us improve
Share bugs, ideas, or general feedback.
From zaileys-official
Diagnoses and fixes runtime errors, exceptions, stack traces, and symptoms (loops, QR issues, missing modules) in zaileys Node/TS WhatsApp framework apps.
How this skill is triggered — by the user, by Claude, or both
Slash command
/zaileys-official:debugThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Diagnose a zaileys failure to root cause, then give the concrete fix with runnable TS.
Share bugs, ideas, or general feedback.
Diagnose a zaileys failure to root cause, then give the concrete fix with runnable TS.
Import is always import { Client } from 'zaileys'. NEVER invent error codes — only the
codes below have real throw sites. Some union codes are reserved (no throw site today) —
respect that and do not present them as live.
Classify the failure. Is it a thrown exception (has an error class and/or a
.code) or a runtime symptom (no exception — a loop, a log line, a missing module, a
silent misbehavior)?
disconnect event; map its
reason (step 5).error event, not a throw (step 5).Identify the class by the operation that failed, then instanceof + switch on .code:
client.send() failing → ZaileysBuilderErrorctx.edit → ZaileysCommandErrorclient.group / .privacy / .newsletter / .community → ZaileysDomainErrorclient.scheduleAt / presence / rate limiter → ZaileysAutomationErrorZaileysStoreErrorMatch .code → cause → fix (tables below). Read err.message for the exact
constraint (counts, ranges, JID, fileName). If err.cause is set (SEND_FAILED,
HANDLER_ERROR, MIDDLEWARE_ERROR, PRESENCE_FAILED, most STORE_*, some
SCHEDULE_INVALID), narrow it (err.cause instanceof Error) and read the real root error.
Match the runtime symptom → cause → fix (Runtime symptoms below).
Connection-level failures: map disconnect reason (fatal vs transient) and ensure
an error listener exists.
Then apply the fix and, if useful, capture a debug log (last section).
import {
ZaileysBuilderError, ZaileysCommandError, ZaileysDomainError,
ZaileysAutomationError, ZaileysStoreError,
} from 'zaileys'
try {
await op()
} catch (err) {
if (err instanceof ZaileysBuilderError) {
switch (err.code) {
case 'EMPTY_CONTENT': /* set content before await */ break
case 'INVALID_OPTIONS': console.error(err.message); break // exact constraint is in .message
case 'SEND_FAILED': console.error('baileys rejected:', err.cause); break // transient → retry
case 'MEDIA_LOAD_FAILED': console.error(err.cause); break
default: console.error(err.code, err.message)
}
} else if (err instanceof ZaileysStoreError) {
if (err.code === 'STORE_NOT_AVAILABLE') {/* install the peer dep named in err.message */}
if (err.code === 'STORE_CORRUPTED') {/* wipe session, re-auth */}
} else throw err
}
All five classes share the shape { readonly code: string; readonly name: string; readonly cause?: unknown }
and extend Error. code is a per-class string-literal union (TS autocompletes it).
ZaileysBuilderError — codes MEDIA_LOAD_FAILED · INVALID_RECIPIENT(reserved) · USERNAME_NOT_FOUND · EMPTY_CONTENT · INVALID_OPTIONS · SEND_FAILED · MESSAGE_NOT_FOUND
| code | cause | fix |
|---|---|---|
INVALID_OPTIONS + msg client not connected | client.send() called before state === 'connected'. This is the connect guard — there is NO NOT_CONNECTED code on the builder. | Guard: if (client.state !== 'connected') return before sending; or send from inside connect/message handlers. |
INVALID_OPTIONS (other) | Catch-all validation failure (poll counts, button ids, location range, list rows, vcard, video mime, etc.). | Read err.message — it states the exact constraint; fix that option. |
EMPTY_CONTENT | A content method got empty input, or builder awaited with no content set (text() requires a non-empty string, no content set). | Call a content method with non-empty input before await: await client.send(jid).text('hi'). |
MEDIA_LOAD_FAILED | Media source couldn't be fetched/read/converted: non-2xx fetch, network error, missing local file, audio transcode or sticker conversion failure. | Verify URL returns 2xx / file exists & readable; ensure ffmpeg/sharp present for audio/sticker; inspect .cause. |
USERNAME_NOT_FOUND | username "<x>" not found — a @username couldn't resolve to a JID. | Pass a raw JID instead (628xxx@s.whatsapp.net), or confirm the username is reachable. |
SEND_FAILED | Socket accepted but rejected / returned no key (incl. interactive content needing relayMessage). .cause = Baileys rejection. | Transient — retry with backoff. Inspect .cause. For buttons/list/carousel/template ensure the socket supports relayMessage. |
MESSAGE_NOT_FOUND | message not found in store for forward — forwarded message absent from store. | Configure a store and ensure the source message was captured before forwarding. |
ZaileysStoreError — codes STORE_NOT_AVAILABLE · STORE_CONNECTION_FAILED · STORE_WRITE_FAILED · STORE_READ_FAILED · STORE_CORRUPTED · STORE_CLOSED (auth + message stores, all backends)
| code | cause | fix |
|---|---|---|
STORE_NOT_AVAILABLE | Missing optional peer dep. Exact msgs: pg is not installed. Run: pnpm add pg, redis peer dependency missing. Run: pnpm add redis, better-sqlite3 belum terpasang. Run: pnpm add better-sqlite3, convex peer dependency missing. Run: pnpm add convex. | Install the peer dep named in err.message (pg / redis / better-sqlite3 / convex). File/Memory adapters need none. |
STORE_CONNECTION_FAILED | Bad config or connect failure: provide either pool or connectionString, not both, pass either client OR url, not both, redis client not open, failed to connect to redis at <url>, failed to migrate ... schema. | Pass exactly one of pool/connectionString (or client/url). await redisClient.connect() first. Inspect .cause. |
STORE_CORRUPTED | failed to parse sqlite blob — stored data unparseable. | Wipe the session/store and re-authenticate. |
STORE_CLOSED | Op after close() (<X>Store is closed). | Recreate the store; don't use after close(). |
STORE_WRITE_FAILED / STORE_READ_FAILED | A write/read op failed. | Inspect .cause (driver, disk, permissions, connectivity, schema). |
ZaileysCommandError — DUPLICATE_COMMAND · INVALID_COMMAND_NAME · HANDLER_ERROR · MIDDLEWARE_ERROR · NO_SENT_MESSAGE · NOT_CONNECTED(reserved)
HANDLER_ERROR → your handler threw; root error on .cause. MIDDLEWARE_ERROR → middleware threw or next() called multiple times. NO_SENT_MESSAGE → ctx.edit requires a prior ctx.reply. DUPLICATE_COMMAND → rename/remove the duplicate. INVALID_COMMAND_NAME → non-empty command spec, no empty segments.ZaileysDomainError — NOT_CONNECTED · GROUP_NOT_FOUND(reserved) · NEWSLETTER_NOT_FOUND · INVALID_PARTICIPANT(reserved) · OPERATION_FAILED
NOT_CONNECTED → wait for the connect event before calling group/privacy/newsletter/community. NEWSLETTER_NOT_FOUND → verify the channel JID. OPERATION_FAILED → invite code/permission issue; read .message.ZaileysAutomationError — NOT_CONNECTED · RATE_LIMIT_INVALID · SCHEDULE_INVALID · PRESENCE_FAILED · TASK_FAILED(reserved) · STORE_UNAVAILABLE(reserved)
SCHEDULE_INVALID → pass a real Date; builder must return content and not throw. RATE_LIMIT_INVALID → every rate value > 0. PRESENCE_FAILED → inspect .cause, retry. NOT_CONNECTED → wait for connect.Reserved codes (in the union, NO throw site today — you will not see them at runtime): builder
INVALID_RECIPIENT; commandNOT_CONNECTED; domainGROUP_NOT_FOUND,INVALID_PARTICIPANT; automationTASK_FAILED,STORE_UNAVAILABLE. Note:client.broadcast()does NOT throw per bad recipient — it resolves{ sent, failed: { jid, error }[] }; inspectfailed.
Full per-code throw-site tables: references/errors.md (in the sibling assist skill).
Default auth path: ./.zaileys/auth/<sessionId> (sessionId default 'default'). Status
lines are prefixed [zaileys] on stderr (statusLog, default true).
| Symptom | Cause | Fix |
|---|---|---|
QR keeps regenerating / "session looks invalid or corrupted" (authenticates then drops; hint appended from attempt 2: bad-session) | Saved creds corrupt; socket never reaches open. | Delete the auth folder and re-auth: rm -rf ./.zaileys (all) or rm -rf ./.zaileys/auth/default (one session — replace default with your sessionId). |
Reconnect loop ([zaileys] Connection lost (<reason>). Reconnecting in <s>s (attempt <n>)...) | Normal auto-reconnect (exp backoff + jitter). Helps only for transient reasons (see disconnect table). | Observe/tune via reconnect option (defaults: enabled:true, maxAttempts:Infinity, initialDelayMs:1000, maxDelayMs:60000, jitterFactor:0.2). Stuck on bad-session → wipe auth folder. reconnect:{enabled:false} to disable. |
Pairing fails before any code (phoneNumber is required when authType is "pairing", phoneNumber must be E.164 with country code, or failed to request pairing code: <reason>) | E.164 validation: digits only, 8–15 long. Last form = passed validation but WhatsApp refused (not on WA / rate-limited). | Pass E.164 with country code, drop leading local 0, no +: phoneNumber: '628123456789'. Wait a few minutes if rate-limited. |
"Cannot find module" for a DB adapter (ZaileysStoreError STORE_NOT_AVAILABLE) | Heavy drivers are optional peer deps, loaded lazily. | Install the one your adapter uses: pg / redis / better-sqlite3 / convex. |
ESM vs require (ERR_REQUIRE_ESM, Cannot use import statement outside a module, require is not defined) | File ext / package.json "type" disagrees with import style. Needs Node >= 20. | Stay consistent: ESM import { Client } from 'zaileys'; CJS const { Client } = require('zaileys'). For TS run npx tsx index.ts; set "module": "NodeNext"/"Bundler". |
Bun ws / WebSocket warnings | Noise from Bun's ws shim, not zaileys (libsignal Closing session: is suppressed). | Benign — ignore. Keep ZAILEYS_DEBUG unset for quiet startup. |
| Bot never sees my own messages | ignoreMe defaults to true (drops fromMe to avoid self-loops). | new Client({ ignoreMe: false }); then gate replies on sender/prefix to avoid an echo loop. |
sharp not installed but images/stickers still work (slower) | sharp is optional, probed opportunistically; falls back to bundled jimp. Missing sharp never throws. | Nothing needed. npm i sharp only for the faster native pipeline. ffmpeg/ffprobe are bundled. |
For Deno (--node-modules-dir), Termux native builds, and file-type/Node-version
mismatches, see references/troubleshooting.md.
Disconnect reasons — client.on('disconnect', ({ reason, willReconnect }) => ...).
reason | fatal? | clears auth? | reconnects? | action |
|---|---|---|---|---|
logged-out | Yes | Yes | No | Re-authenticate (new QR / pairing). |
connection-replaced | Yes | Yes | No | Same account opened elsewhere; one socket only. |
forbidden | Yes | Yes | No | Account blocked; manual intervention. |
bad-session | No | Yes | Yes | Auth cleared, reconnects — but corrupt-on-disk loops; wipe auth folder. |
restart-required / connection-closed / connection-lost / multi-device-mismatch / unavailable-service / unknown | No | No | Yes | Transient — zaileys reconnects automatically. |
Fatal = logged-out ∨ connection-replaced ∨ forbidden → willReconnect: false.
Background failures (chiefly auto-connect) surface via the error event, NOT a throw — and
only if a listener is already attached before connection starts. With autoConnect (default
true), register it synchronously right after construction or failures are swallowed.
const client = new Client({ authType: 'qr' })
client.on('error', ({ sessionId, error }) => console.error(`[${sessionId}]`, error.message))
client.on('disconnect', ({ reason, willReconnect }) => {
if (!willReconnect) console.error('fatal disconnect — re-auth required:', reason)
})
client.on('reconnecting', ({ attempt, delayMs, reason }) => console.log(`retry #${attempt} in ${delayMs}ms (${reason})`))
await client.disconnect() — closes socket, keeps creds (reconnect later, no re-scan).await client.logout() — unlinks the device on WhatsApp + wipes creds; next run needs a fresh QR/pairing. Emits final disconnect reason: 'logged-out'.rm -rf ./.zaileys/auth/default. With a DB auth adapter, logout() clears rows there instead of a folder.[zaileys] status lines (statusLog) are separate from ZAILEYS_DEBUG (pino, default silent).
ZAILEYS_DEBUG=1 node index.js # info
ZAILEYS_DEBUG=debug npx tsx index.ts # verbose
ZAILEYS_DEBUG=trace npx tsx index.ts 2>&1 | tee zaileys-debug.log # full bug report
Levels: fatal|error|warn|info|debug|trace; anything else → silent. statusLog: false
silences the [zaileys] lines. Report at github.com/zeative/zaileys with the log + runtime version.
references/errors.md (sibling assist skill) — every class + .code with exact throw sites.references/troubleshooting.md (sibling assist skill) — all runtime symptoms.These are authoritative and kept in sync with the code — fetch them when you need more detail, the newest API, or to verify before answering (do not guess when unsure):
/getting-started · /installation · /configuration · /client · /events · /sending-messages · /media · /interactive · /rich-responses · /commands · /automation · /storage · /error-handling · /runtimes · /troubleshooting · /api-reference (e.g. https://zeative.github.io/zaileys/sending-messages)npx claudepluginhub zeative/zaileys --plugin zaileys-officialOrchestrates zaileys tasks — build, debug, review, or implement features for the zaileys WhatsApp framework. Activates on any zaileys-related request.
Maintains, debugs, and deploys autorun hooks for Claude Code and Gemini CLI. Use for fixing hooks, debugging errors, updating versions, zombie states, invisible failures, and log diagnostics.
Guides systematic debugging for broken features, errors, failed deployments, or tests: reproduce bugs, gather git/logs diagnostics, read errors, diagnose root causes before fixes.