{"owner":"WhiskeySockets","repo":"Baileys","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","AGENTS.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file is read by Claude Code at the start of every session in this repo.\n\nThe contributor and AI-agent guide lives in **[AGENTS.md](AGENTS.md)** — start there. It covers repo layout, setup, daily commands, code style, commit conventions, and what not to touch.\n\nFor AI authorship disclosure rules and the broader AI policy, see **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** § AI Policy.\n\nFor security-sensitive changes and vulnerability disclosure, see **[SECURITY.md](SECURITY.md)**.\n","AGENTS.md":"# AGENTS.md\n\nGuide for AI coding agents (Claude Code, Cursor, Aider, Codex, Copilot Workspace, etc.) contributing to Baileys. Human contributors should also read this — there's nothing AI-specific in the conventions, only in the disclosure rules at the end.\n\nIf you are an AI agent driving this repo, read this file first, then `CODE_OF_CONDUCT.md` (specifically the AI policy section), then `SECURITY.md`.\n\n## What Baileys is\n\nA TypeScript WebSocket client for the WhatsApp Web protocol. No browser, no Selenium — it speaks the binary Noise/protobuf protocol directly. Used by thousands of downstream projects, so changes to public APIs and wire-level handling have wide blast radius.\n\nThe library is **dual-use**: legitimate automation, bots, and integrations on one side; spam, stalkerware, and ToS-breaking automation on the other. We do not accept contributions whose primary purpose is to enable abuse (mass messaging, evasion of WhatsApp's anti-spam, scraping users without consent). See `CODE_OF_CONDUCT.md`.\n\n## Repository layout\n\n```\nsrc/\n  Socket/          High-level socket — chats, groups, messages send/recv, newsletter, USync\n  Signal/          Signal Protocol session/sender-key wrapping over libsignal-node\n  Utils/           Decoding, media, auth state, retry, app-state sync, generics\n  Types/           Public TypeScript types — touching these is a public-API change\n  WABinary/        Binary node encoding/decoding\n  WAUSync/         USync query protocols\n  Defaults/        Constants (WA Web version, baileys version, default config)\n  __tests__/       Jest unit + integration tests; e2e tests live in __tests__/e2e\nWAProto/           Generated protobuf bindings — DO NOT hand-edit\nExample/           Reference implementation in example.ts\nproto-extract/     Tooling to refresh protobufs from WA Web\nscripts/           Repo automation (version bumps, etc.)\n.github/workflows/ CI — lint, test, e2e, build, release\n```\n\n`WAProto/index.js`, `WAProto/index.d.ts` are generated by `WAProto/GenerateStatics.sh`. If a change requires modifying them, regenerate via `npm run gen:protobuf` rather than editing by hand.\n\n## Setup\n\nThis repo requires **Yarn 4** via Corepack. Yarn 1 / classic will fail noisily on `package.json` resolutions.\n\n```bash\ncorepack enable\nyarn install\n```\n\nNode ≥ 20 (enforced by `engines` and `preinstall`).\n\n## Daily commands\n\n| Task | Command |\n|---|---|\n| Install | `yarn install` |\n| Build (lib + types) | `yarn build` |\n| Type-check + lint | `yarn lint` |\n| Auto-fix lint + format | `yarn lint:fix` |\n| Format only | `yarn format` |\n| Unit + integration tests | `yarn test` |\n| End-to-end tests | `yarn test:e2e` (requires the bartender mock server, see below) |\n| Run the example | `yarn example` |\n| Regenerate protobufs | `yarn gen:protobuf` |\n| Audit deps | `yarn npm audit --recursive` |\n\n`yarn lint` runs `tsc` first, then ESLint. A green lint means no type errors.\n\n## Code style\n\n- **TypeScript strict** — `strict`, `strictNullChecks`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax` are all on. Don't disable them locally.\n- **Tabs** for indentation, single quotes, no semicolons (Prettier-enforced).\n- **No `any` in new code.** Existing `any`s in tests are tolerated as warnings, not invitations.\n- **No comments that restate the code.** Comment the *why* — protocol quirks, WhatsApp-side behavior, non-obvious workarounds. Don't comment-narrate \"// loop over messages\".\n- **No emojis in code or commit messages** unless the user explicitly asks.\n- **Named exports** preferred. Default exports only where they already exist (e.g., `makeWASocket`).\n- **Errors**: throw `Boom` (`@hapi/boom`) for protocol/HTTP-style errors so downstream code can branch on `.output.statusCode`. Plain `Error` for everything else.\n- **Logging**: every code path that crosses an async boundary should accept a `logger: ILogger` (pino-compatible). Don't `console.log`.\n\n## Idiomatic patterns\n\nThese are the patterns the existing code uses. Match them. New code that does the same thing differently will get review comments asking you to align.\n\n### Errors — Boom with statusCode\n\n```ts\nimport { Boom } from '@hapi/boom'\n\nif (!sock.user) {\n  throw new Boom('Not authenticated', { statusCode: 401 })\n}\n\nif (!isJidUser(jid)) {\n  throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 })\n}\n```\n\nDownstream code branches on `error.output.statusCode` to retry, log out, or surface to the user. Plain `throw new Error(...)` loses that signal. Reserve plain `Error` for genuinely internal invariants where no caller is expected to recover.\n\n### Logging — structured, never positional\n\n```ts\n// good: object first, message last; reads cleanly in JSON logs\nlogger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted')\nlogger.debug({ messageKey }, 'already requested resend')\nlogger.error({ err: error, opName }, 'failed to parse mex notification JSON')\n\n// bad: string interpolation, untyped fields\nlogger.warn(`error 463 from ${attrs.from}`)\nconsole.log('failed:', error)\n```\n\nPino convention: errors go under `err`, not `error` or `e`. The structured object is the *first* argument so log processors can index it.\n\n### JIDs — always go through the helpers\n\n```ts\nimport { jidDecode, jidNormalizedUser, areJidsSameUser, isJidUser } from '../WABinary'\n\nconst decoded = jidDecode(rawJid)            // { user, server, device?, agent? } | undefined\nconst normalized = jidNormalizedUser(rawJid) // strips device/agent, lowercases\nconst same = areJidsSameUser(a, b)           // compare user portions only\n```\n\nNever split a JID with `.split('@')` or compare with `===`. JIDs carry device suffixes (`:0`, `:42`), agent fields, and LID/PN duality — string ops will silently miss matches and you'll ship a bug that only fires on multi-device accounts.\n\n### Binary nodes — typed accessors\n\n```ts\nimport { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'\n\nconst groupsNode = getBinaryNodeChild(result, 'groups')\nif (!groupsNode) {\n  throw new Boom('missing <groups> in iq response', { statusCode: 502 })\n}\n\nconst groups = getBinaryNodeChildren(groupsNode, 'group') // BinaryNode[]\nconst text = getBinaryNodeChildString(node, 'body')        // string | undefined\nconst { attrs } = node                                     // typed Record<string, string>\n```\n\nDon't reach into `node.content` as an array directly — types are loose and the shape varies by stanza. The accessors handle the missing/single/array cases.\n\n### Sending IQs — `query` with timeouts\n\n```ts\nconst result = await sock.query({\n  tag: 'iq',\n  attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' },\n  content: [{ tag: 'picture', attrs: { type: 'image', query: 'url' } }]\n}, /* timeoutMs */ 15_000)\n```\n\n`query` auto-generates the stanza id, attaches a one-shot listener, and rejects on timeout. Don't write your own `sock.ws.send` + manual listener — you'll leak listeners on errors.\n\n### Listening for incoming stanzas — `CB:` prefix\n\n```ts\nsock.ws.on('CB:ib,,dirty', async (node: BinaryNode) => {\n  const { attrs } = getBinaryNodeChild(node, 'dirty')!\n  // ...\n})\n\nsock.ws.on('CB:notification,type:server_sync', handler)\n```\n\nThe `CB:tag,attr:value` syntax routes by tag + attribute filter inside the websocket. This is how `messages-recv`, `chats`, `groups` listen — don't filter manually inside a generic `'message'` handler.\n\n### Public events — `ev.on`, never call twice\n\n```ts\nsock.ev.on('messages.upsert', ({ messages, type }) => { ... })\nsock.ev.on('connection.update', ({ connection, lastDisconnect }) => { ... })\nsock.ev.on('creds.update', saveCreds)\n```\n\n`saveCreds` (or any handler) must be deduped — registering twice means writing twice. The harness in `__tests__/e2e/helpers/test-client.ts` shows the cleanup pattern (`ev.off` in teardown).\n\n### Async cleanup — bracket pattern\n\nWhen you allocate a resource (timer, listener, ws subscription) inside a Promise, clean it up in *both* paths:\n\n```ts\nreturn new Promise<T>((resolve, reject) => {\n  const timer = setTimeout(() => {\n    cleanup()\n    reject(new Boom('timed out', { statusCode: 408 }))\n  }, timeoutMs)\n\n  const cleanup = () => {\n    clearTimeout(timer)\n    sock.ev.off('event.name', handler)\n  }\n\n  const handler = (data: T) => {\n    if (matches(data)) {\n      cleanup()\n      resolve(data)\n    }\n  }\n\n  sock.ev.on('event.name', handler)\n})\n```\n\nHalf-cleanups are how this codebase grew its memory leaks. The bracket pattern (allocate → cleanup defined → both paths call it) is the fix.\n\n### Imports — `verbatimModuleSyntax`\n\n```ts\nimport type { WAMessage, WAUrlInfo } from '../Types'\nimport { Boom } from '@hapi/boom'\nimport { type BinaryNode, getBinaryNodeChild } from '../WABinary'\n```\n\nType-only imports must be marked `import type` or inline-prefixed `type` — TS strict-mode `verbatimModuleSyntax` will fail the build otherwise. Don't merge a value import with a type import unless you actually use both at runtime.\n\n### Optional chains over null guards\n\n```ts\n// good\nconst text = msg.message?.extendedTextMessage?.text ?? msg.message?.conversation\nif (!sent?.key.id) return\n\n// bad — proliferates `if (x && x.y && x.y.z)` ladders\nif (msg.message && msg.message.extendedTextMessage) { ... }\n```\n\n`noUncheckedIndexedAccess` is on, so array/record indexing returns `T | undefined`. Don't paper over it with `!` unless you have an invariant the type system can't see — and if you do, leave a one-line comment explaining the invariant.\n\n### Tests — colocate, name by behavior\n\n```ts\n// src/__tests__/Utils/decode-wa-message.test.ts\ndescribe('SERVER_ERROR_CODES', () => {\n  it('MessageAccountRestriction is 463', () => {\n    expect(SERVER_ERROR_CODES.MessageAccountRestriction).toBe('463')\n  })\n})\n```\n\nTest files mirror the source path: `src/Foo/bar.ts` → `src/__tests__/Foo/bar.test.ts`. `describe` names the unit, `it` names the behavior in plain English. Avoid `it('works')`.\n\n## Public API discipline\n\n`src/Types/**` and the top-level `src/index.ts` re-exports define the public surface. Treat changes there as breaking unless you can prove additive-only.\n\nFor wire-level changes (`Socket/messages-recv.ts`, `Utils/decode-wa-message.ts`, `WABinary/`, etc.) — describe the WhatsApp-side trigger in the PR. Reviewers can't always reproduce protocol behavior, so the description does the heavy lifting.\n\n## Commits and PRs\n\nConventional commits, scoped where useful:\n\n```\nfeat(socket): add support for reachout limits XWAs\nfix(retry): process <keys> bundle and embed SKDM on resend\nchore(deps): bump ajv from 6.12.6 to 6.15.0\ntest(e2e): test harness + signal/prekey fixes\n```\n\nPR titles follow the same convention. Squash-merge is the default.\n\nBefore opening a PR:\n1. `yarn lint` — must be 0 errors.\n2. `yarn test` — must be all green. If you can't run e2e locally, say so in the PR description.\n3. Don't commit `baileys_auth_info/`, `.env`, `mitm_*.db`, `.superset/`, or any session state. Git is configured to ignore the obvious ones; double-check.\n4. Don't commit regenerated `yarn.lock` from a different package manager. If your `yarn.lock` diff is unexpectedly large (thousands of lines for a one-line `package.json` change), you're using Yarn 1 — switch to Corepack.\n\n## What not to touch without coordination\n\n- **`libsignal` cryptographic flows** — session state, prekeys, sender-key derivation. Subtle bugs here are silent and brick downstream sessions.\n- **`Defaults/baileys-version.json`** — bumped by the `update-version` workflow. Manual edits race with automation.\n- **`WAProto/` generated files** — regenerate, don't hand-edit.\n- **`.github/workflows/`** — CI changes are reviewed separately; bundle them in their own PR when possible.\n- **`package.json` `resolutions`** — these patch known security advisories. Removing entries reintroduces CVEs; check `yarn npm audit --recursive` before pruning.\n\n## Testing expectations\n\n- New public API → unit test in `src/__tests__/`.\n- New protocol path or stanza handler → integration test mocking the binary node, *not* an e2e test (e2e is expensive and flaky in agent loops).\n- New crypto/auth flow → e2e against bartender if feasible, otherwise a deterministic fixture-based unit test.\n\nTests are colocated by area: `src/__tests__/Socket/`, `src/__tests__/Utils/`, `src/__tests__/binary/`. Match the existing layout.\n\n## Security-sensitive changes\n\nIf your change touches:\n- Auth state read/write, key storage, prekey/session lifecycle\n- Message decryption / signature verification\n- Any path that handles user PII (phone numbers, JIDs, message content) in logs or errors\n\n…flag it explicitly in the PR description. See `SECURITY.md` for disclosure of vulnerabilities (do not file them as public issues).\n\n## AI agent etiquette\n\nBeyond the AI policy in `CODE_OF_CONDUCT.md`:\n\n- **Read before you write.** This codebase has subtle protocol invariants. A grep-and-replace agent will make a mess of `Socket/messages-recv.ts`. Read the surrounding handler before editing.\n- **Don't invent WhatsApp protocol details.** If you're not sure how a stanza is structured, find a real example in the tests or in `Utils/decode-wa-message.ts`. Hallucinated XML attributes get merged and then break in production.\n- **Stay in scope.** A bug fix doesn't need a refactor pass. A type tweak doesn't need a comment cleanup PR.\n- **Don't paste auth state into AI tools.** `baileys_auth_info/` contains long-lived Signal keys. Treat it like an SSH private key.\n- **Disclose AI authorship in PRs.** A one-line \"drafted with [tool], reviewed by [human]\" is enough. See `CODE_OF_CONDUCT.md` § AI Policy for the full rule.\n\n## Where to ask\n\n- Discord: https://discord.gg/WeJM5FP9GG\n- Wiki: https://baileys.wiki\n- Security: see `SECURITY.md`\n\nIf you're an agent and you're stuck on something this file doesn't cover, fall back to reading the relevant `src/` directory and the matching tests — they're the source of truth.\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file is read by Claude Code at the start of every session in this repo.\n\nThe contributor and AI-agent guide lives in **[AGENTS.md](AGENTS.md)** — start there. It covers repo layout, setup, daily commands, code style, commit conventions, and what not to touch.\n\nFor AI authorship disclosure rules and the broader AI policy, see **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** § AI Policy.\n\nFor security-sensitive changes and vulnerability disclosure, see **[SECURITY.md](SECURITY.md)**.\n","AGENTS.md":"# AGENTS.md\n\nGuide for AI coding agents (Claude Code, Cursor, Aider, Codex, Copilot Workspace, etc.) contributing to Baileys. Human contributors should also read this — there's nothing AI-specific in the conventions, only in the disclosure rules at the end.\n\nIf you are an AI agent driving this repo, read this file first, then `CODE_OF_CONDUCT.md` (specifically the AI policy section), then `SECURITY.md`.\n\n## What Baileys is\n\nA TypeScript WebSocket client for the WhatsApp Web protocol. No browser, no Selenium — it speaks the binary Noise/protobuf protocol directly. Used by thousands of downstream projects, so changes to public APIs and wire-level handling have wide blast radius.\n\nThe library is **dual-use**: legitimate automation, bots, and integrations on one side; spam, stalkerware, and ToS-breaking automation on the other. We do not accept contributions whose primary purpose is to enable abuse (mass messaging, evasion of WhatsApp's anti-spam, scraping users without consent). See `CODE_OF_CONDUCT.md`.\n\n## Repository layout\n\n```\nsrc/\n  Socket/          High-level socket — chats, groups, messages send/recv, newsletter, USync\n  Signal/          Signal Protocol session/sender-key wrapping over libsignal-node\n  Utils/           Decoding, media, auth state, retry, app-state sync, generics\n  Types/           Public TypeScript types — touching these is a public-API change\n  WABinary/        Binary node encoding/decoding\n  WAUSync/         USync query protocols\n  Defaults/        Constants (WA Web version, baileys version, default config)\n  __tests__/       Jest unit + integration tests; e2e tests live in __tests__/e2e\nWAProto/           Generated protobuf bindings — DO NOT hand-edit\nExample/           Reference implementation in example.ts\nproto-extract/     Tooling to refresh protobufs from WA Web\nscripts/           Repo automation (version bumps, etc.)\n.github/workflows/ CI — lint, test, e2e, build, release\n```\n\n`WAProto/index.js`, `WAProto/index.d.ts` are generated by `WAProto/GenerateStatics.sh`. If a change requires modifying them, regenerate via `npm run gen:protobuf` rather than editing by hand.\n\n## Setup\n\nThis repo requires **Yarn 4** via Corepack. Yarn 1 / classic will fail noisily on `package.json` resolutions.\n\n```bash\ncorepack enable\nyarn install\n```\n\nNode ≥ 20 (enforced by `engines` and `preinstall`).\n\n## Daily commands\n\n| Task | Command |\n|---|---|\n| Install | `yarn install` |\n| Build (lib + types) | `yarn build` |\n| Type-check + lint | `yarn lint` |\n| Auto-fix lint + format | `yarn lint:fix` |\n| Format only | `yarn format` |\n| Unit + integration tests | `yarn test` |\n| End-to-end tests | `yarn test:e2e` (requires the bartender mock server, see below) |\n| Run the example | `yarn example` |\n| Regenerate protobufs | `yarn gen:protobuf` |\n| Audit deps | `yarn npm audit --recursive` |\n\n`yarn lint` runs `tsc` first, then ESLint. A green lint means no type errors.\n\n## Code style\n\n- **TypeScript strict** — `strict`, `strictNullChecks`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax` are all on. Don't disable them locally.\n- **Tabs** for indentation, single quotes, no semicolons (Prettier-enforced).\n- **No `any` in new code.** Existing `any`s in tests are tolerated as warnings, not invitations.\n- **No comments that restate the code.** Comment the *why* — protocol quirks, WhatsApp-side behavior, non-obvious workarounds. Don't comment-narrate \"// loop over messages\".\n- **No emojis in code or commit messages** unless the user explicitly asks.\n- **Named exports** preferred. Default exports only where they already exist (e.g., `makeWASocket`).\n- **Errors**: throw `Boom` (`@hapi/boom`) for protocol/HTTP-style errors so downstream code can branch on `.output.statusCode`. Plain `Error` for everything else.\n- **Logging**: every code path that crosses an async boundary should accept a `logger: ILogger` (pino-compatible). Don't `console.log`.\n\n## Idiomatic patterns\n\nThese are the patterns the existing code uses. Match them. New code that does the same thing differently will get review comments asking you to align.\n\n### Errors — Boom with statusCode\n\n```ts\nimport { Boom } from '@hapi/boom'\n\nif (!sock.user) {\n  throw new Boom('Not authenticated', { statusCode: 401 })\n}\n\nif (!isJidUser(jid)) {\n  throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 })\n}\n```\n\nDownstream code branches on `error.output.statusCode` to retry, log out, or surface to the user. Plain `throw new Error(...)` loses that signal. Reserve plain `Error` for genuinely internal invariants where no caller is expected to recover.\n\n### Logging — structured, never positional\n\n```ts\n// good: object first, message last; reads cleanly in JSON logs\nlogger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted')\nlogger.debug({ messageKey }, 'already requested resend')\nlogger.error({ err: error, opName }, 'failed to parse mex notification JSON')\n\n// bad: string interpolation, untyped fields\nlogger.warn(`error 463 from ${attrs.from}`)\nconsole.log('failed:', error)\n```\n\nPino convention: errors go under `err`, not `error` or `e`. The structured object is the *first* argument so log processors can index it.\n\n### JIDs — always go through the helpers\n\n```ts\nimport { jidDecode, jidNormalizedUser, areJidsSameUser, isJidUser } from '../WABinary'\n\nconst decoded = jidDecode(rawJid)            // { user, server, device?, agent? } | undefined\nconst normalized = jidNormalizedUser(rawJid) // strips device/agent, lowercases\nconst same = areJidsSameUser(a, b)           // compare user portions only\n```\n\nNever split a JID with `.split('@')` or compare with `===`. JIDs carry device suffixes (`:0`, `:42`), agent fields, and LID/PN duality — string ops will silently miss matches and you'll ship a bug that only fires on multi-device accounts.\n\n### Binary nodes — typed accessors\n\n```ts\nimport { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'\n\nconst groupsNode = getBinaryNodeChild(result, 'groups')\nif (!groupsNode) {\n  throw new Boom('missing <groups> in iq response', { statusCode: 502 })\n}\n\nconst groups = getBinaryNodeChildren(groupsNode, 'group') // BinaryNode[]\nconst text = getBinaryNodeChildString(node, 'body')        // string | undefined\nconst { attrs } = node                                     // typed Record<string, string>\n```\n\nDon't reach into `node.content` as an array directly — types are loose and the shape varies by stanza. The accessors handle the missing/single/array cases.\n\n### Sending IQs — `query` with timeouts\n\n```ts\nconst result = await sock.query({\n  tag: 'iq',\n  attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' },\n  content: [{ tag: 'picture', attrs: { type: 'image', query: 'url' } }]\n}, /* timeoutMs */ 15_000)\n```\n\n`query` auto-generates the stanza id, attaches a one-shot listener, and rejects on timeout. Don't write your own `sock.ws.send` + manual listener — you'll leak listeners on errors.\n\n### Listening for incoming stanzas — `CB:` prefix\n\n```ts\nsock.ws.on('CB:ib,,dirty', async (node: BinaryNode) => {\n  const { attrs } = getBinaryNodeChild(node, 'dirty')!\n  // ...\n})\n\nsock.ws.on('CB:notification,type:server_sync', handler)\n```\n\nThe `CB:tag,attr:value` syntax routes by tag + attribute filter inside the websocket. This is how `messages-recv`, `chats`, `groups` listen — don't filter manually inside a generic `'message'` handler.\n\n### Public events — `ev.on`, never call twice\n\n```ts\nsock.ev.on('messages.upsert', ({ messages, type }) => { ... })\nsock.ev.on('connection.update', ({ connection, lastDisconnect }) => { ... })\nsock.ev.on('creds.update', saveCreds)\n```\n\n`saveCreds` (or any handler) must be deduped — registering twice means writing twice. The harness in `__tests__/e2e/helpers/test-client.ts` shows the cleanup pattern (`ev.off` in teardown).\n\n### Async cleanup — bracket pattern\n\nWhen you allocate a resource (timer, listener, ws subscription) inside a Promise, clean it up in *both* paths:\n\n```ts\nreturn new Promise<T>((resolve, reject) => {\n  const timer = setTimeout(() => {\n    cleanup()\n    reject(new Boom('timed out', { statusCode: 408 }))\n  }, timeoutMs)\n\n  const cleanup = () => {\n    clearTimeout(timer)\n    sock.ev.off('event.name', handler)\n  }\n\n  const handler = (data: T) => {\n    if (matches(data)) {\n      cleanup()\n      resolve(data)\n    }\n  }\n\n  sock.ev.on('event.name', handler)\n})\n```\n\nHalf-cleanups are how this codebase grew its memory leaks. The bracket pattern (allocate → cleanup defined → both paths call it) is the fix.\n\n### Imports — `verbatimModuleSyntax`\n\n```ts\nimport type { WAMessage, WAUrlInfo } from '../Types'\nimport { Boom } from '@hapi/boom'\nimport { type BinaryNode, getBinaryNodeChild } from '../WABinary'\n```\n\nType-only imports must be marked `import type` or inline-prefixed `type` — TS strict-mode `verbatimModuleSyntax` will fail the build otherwise. Don't merge a value import with a type import unless you actually use both at runtime.\n\n### Optional chains over null guards\n\n```ts\n// good\nconst text = msg.message?.extendedTextMessage?.text ?? msg.message?.conversation\nif (!sent?.key.id) return\n\n// bad — proliferates `if (x && x.y && x.y.z)` ladders\nif (msg.message && msg.message.extendedTextMessage) { ... }\n```\n\n`noUncheckedIndexedAccess` is on, so array/record indexing returns `T | undefined`. Don't paper over it with `!` unless you have an invariant the type system can't see — and if you do, leave a one-line comment explaining the invariant.\n\n### Tests — colocate, name by behavior\n\n```ts\n// src/__tests__/Utils/decode-wa-message.test.ts\ndescribe('SERVER_ERROR_CODES', () => {\n  it('MessageAccountRestriction is 463', () => {\n    expect(SERVER_ERROR_CODES.MessageAccountRestriction).toBe('463')\n  })\n})\n```\n\nTest files mirror the source path: `src/Foo/bar.ts` → `src/__tests__/Foo/bar.test.ts`. `describe` names the unit, `it` names the behavior in plain English. Avoid `it('works')`.\n\n## Public API discipline\n\n`src/Types/**` and the top-level `src/index.ts` re-exports define the public surface. Treat changes there as breaking unless you can prove additive-only.\n\nFor wire-level changes (`Socket/messages-recv.ts`, `Utils/decode-wa-message.ts`, `WABinary/`, etc.) — describe the WhatsApp-side trigger in the PR. Reviewers can't always reproduce protocol behavior, so the description does the heavy lifting.\n\n## Commits and PRs\n\nConventional commits, scoped where useful:\n\n```\nfeat(socket): add support for reachout limits XWAs\nfix(retry): process <keys> bundle and embed SKDM on resend\nchore(deps): bump ajv from 6.12.6 to 6.15.0\ntest(e2e): test harness + signal/prekey fixes\n```\n\nPR titles follow the same convention. Squash-merge is the default.\n\nBefore opening a PR:\n1. `yarn lint` — must be 0 errors.\n2. `yarn test` — must be all green. If you can't run e2e locally, say so in the PR description.\n3. Don't commit `baileys_auth_info/`, `.env`, `mitm_*.db`, `.superset/`, or any session state. Git is configured to ignore the obvious ones; double-check.\n4. Don't commit regenerated `yarn.lock` from a different package manager. If your `yarn.lock` diff is unexpectedly large (thousands of lines for a one-line `package.json` change), you're using Yarn 1 — switch to Corepack.\n\n## What not to touch without coordination\n\n- **`libsignal` cryptographic flows** — session state, prekeys, sender-key derivation. Subtle bugs here are silent and brick downstream sessions.\n- **`Defaults/baileys-version.json`** — bumped by the `update-version` workflow. Manual edits race with automation.\n- **`WAProto/` generated files** — regenerate, don't hand-edit.\n- **`.github/workflows/`** — CI changes are reviewed separately; bundle them in their own PR when possible.\n- **`package.json` `resolutions`** — these patch known security advisories. Removing entries reintroduces CVEs; check `yarn npm audit --recursive` before pruning.\n\n## Testing expectations\n\n- New public API → unit test in `src/__tests__/`.\n- New protocol path or stanza handler → integration test mocking the binary node, *not* an e2e test (e2e is expensive and flaky in agent loops).\n- New crypto/auth flow → e2e against bartender if feasible, otherwise a deterministic fixture-based unit test.\n\nTests are colocated by area: `src/__tests__/Socket/`, `src/__tests__/Utils/`, `src/__tests__/binary/`. Match the existing layout.\n\n## Security-sensitive changes\n\nIf your change touches:\n- Auth state read/write, key storage, prekey/session lifecycle\n- Message decryption / signature verification\n- Any path that handles user PII (phone numbers, JIDs, message content) in logs or errors\n\n…flag it explicitly in the PR description. See `SECURITY.md` for disclosure of vulnerabilities (do not file them as public issues).\n\n## AI agent etiquette\n\nBeyond the AI policy in `CODE_OF_CONDUCT.md`:\n\n- **Read before you write.** This codebase has subtle protocol invariants. A grep-and-replace agent will make a mess of `Socket/messages-recv.ts`. Read the surrounding handler before editing.\n- **Don't invent WhatsApp protocol details.** If you're not sure how a stanza is structured, find a real example in the tests or in `Utils/decode-wa-message.ts`. Hallucinated XML attributes get merged and then break in production.\n- **Stay in scope.** A bug fix doesn't need a refactor pass. A type tweak doesn't need a comment cleanup PR.\n- **Don't paste auth state into AI tools.** `baileys_auth_info/` contains long-lived Signal keys. Treat it like an SSH private key.\n- **Disclose AI authorship in PRs.** A one-line \"drafted with [tool], reviewed by [human]\" is enough. See `CODE_OF_CONDUCT.md` § AI Policy for the full rule.\n\n## Where to ask\n\n- Discord: https://discord.gg/WeJM5FP9GG\n- Wiki: https://baileys.wiki\n- Security: see `SECURITY.md`\n\nIf you're an agent and you're stuck on something this file doesn't cover, fall back to reading the relevant `src/` directory and the matching tests — they're the source of truth.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# CLAUDE.md\n\nThis file is read by Claude Code at the start of every session in this repo.\n\nThe contributor and AI-agent guide lives in **[AGENTS.md](AGENTS.md)** — start there. It covers repo layout, setup, daily commands, code style, commit conventions, and what not to touch.\n\nFor AI authorship disclosure rules and the broader AI policy, see **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** § AI Policy.\n\nFor security-sensitive changes and vulnerability disclosure, see **[SECURITY.md](SECURITY.md)**.\n","category":"root","tokens":126},{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nGuide for AI coding agents (Claude Code, Cursor, Aider, Codex, Copilot Workspace, etc.) contributing to Baileys. Human contributors should also read this — there's nothing AI-specific in the conventions, only in the disclosure rules at the end.\n\nIf you are an AI agent driving this repo, read this file first, then `CODE_OF_CONDUCT.md` (specifically the AI policy section), then `SECURITY.md`.\n\n## What Baileys is\n\nA TypeScript WebSocket client for the WhatsApp Web protocol. No browser, no Selenium — it speaks the binary Noise/protobuf protocol directly. Used by thousands of downstream projects, so changes to public APIs and wire-level handling have wide blast radius.\n\nThe library is **dual-use**: legitimate automation, bots, and integrations on one side; spam, stalkerware, and ToS-breaking automation on the other. We do not accept contributions whose primary purpose is to enable abuse (mass messaging, evasion of WhatsApp's anti-spam, scraping users without consent). See `CODE_OF_CONDUCT.md`.\n\n## Repository layout\n\n```\nsrc/\n  Socket/          High-level socket — chats, groups, messages send/recv, newsletter, USync\n  Signal/          Signal Protocol session/sender-key wrapping over libsignal-node\n  Utils/           Decoding, media, auth state, retry, app-state sync, generics\n  Types/           Public TypeScript types — touching these is a public-API change\n  WABinary/        Binary node encoding/decoding\n  WAUSync/         USync query protocols\n  Defaults/        Constants (WA Web version, baileys version, default config)\n  __tests__/       Jest unit + integration tests; e2e tests live in __tests__/e2e\nWAProto/           Generated protobuf bindings — DO NOT hand-edit\nExample/           Reference implementation in example.ts\nproto-extract/     Tooling to refresh protobufs from WA Web\nscripts/           Repo automation (version bumps, etc.)\n.github/workflows/ CI — lint, test, e2e, build, release\n```\n\n`WAProto/index.js`, `WAProto/index.d.ts` are generated by `WAProto/GenerateStatics.sh`. If a change requires modifying them, regenerate via `npm run gen:protobuf` rather than editing by hand.\n\n## Setup\n\nThis repo requires **Yarn 4** via Corepack. Yarn 1 / classic will fail noisily on `package.json` resolutions.\n\n```bash\ncorepack enable\nyarn install\n```\n\nNode ≥ 20 (enforced by `engines` and `preinstall`).\n\n## Daily commands\n\n| Task | Command |\n|---|---|\n| Install | `yarn install` |\n| Build (lib + types) | `yarn build` |\n| Type-check + lint | `yarn lint` |\n| Auto-fix lint + format | `yarn lint:fix` |\n| Format only | `yarn format` |\n| Unit + integration tests | `yarn test` |\n| End-to-end tests | `yarn test:e2e` (requires the bartender mock server, see below) |\n| Run the example | `yarn example` |\n| Regenerate protobufs | `yarn gen:protobuf` |\n| Audit deps | `yarn npm audit --recursive` |\n\n`yarn lint` runs `tsc` first, then ESLint. A green lint means no type errors.\n\n## Code style\n\n- **TypeScript strict** — `strict`, `strictNullChecks`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax` are all on. Don't disable them locally.\n- **Tabs** for indentation, single quotes, no semicolons (Prettier-enforced).\n- **No `any` in new code.** Existing `any`s in tests are tolerated as warnings, not invitations.\n- **No comments that restate the code.** Comment the *why* — protocol quirks, WhatsApp-side behavior, non-obvious workarounds. Don't comment-narrate \"// loop over messages\".\n- **No emojis in code or commit messages** unless the user explicitly asks.\n- **Named exports** preferred. Default exports only where they already exist (e.g., `makeWASocket`).\n- **Errors**: throw `Boom` (`@hapi/boom`) for protocol/HTTP-style errors so downstream code can branch on `.output.statusCode`. Plain `Error` for everything else.\n- **Logging**: every code path that crosses an async boundary should accept a `logger: ILogger` (pino-compatible). Don't `console.log`.\n\n## Idiomatic patterns\n\nThese are the patterns the existing code uses. Match them. New code that does the same thing differently will get review comments asking you to align.\n\n### Errors — Boom with statusCode\n\n```ts\nimport { Boom } from '@hapi/boom'\n\nif (!sock.user) {\n  throw new Boom('Not authenticated', { statusCode: 401 })\n}\n\nif (!isJidUser(jid)) {\n  throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 })\n}\n```\n\nDownstream code branches on `error.output.statusCode` to retry, log out, or surface to the user. Plain `throw new Error(...)` loses that signal. Reserve plain `Error` for genuinely internal invariants where no caller is expected to recover.\n\n### Logging — structured, never positional\n\n```ts\n// good: object first, message last; reads cleanly in JSON logs\nlogger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted')\nlogger.debug({ messageKey }, 'already requested resend')\nlogger.error({ err: error, opName }, 'failed to parse mex notification JSON')\n\n// bad: string interpolation, untyped fields\nlogger.warn(`error 463 from ${attrs.from}`)\nconsole.log('failed:', error)\n```\n\nPino convention: errors go under `err`, not `error` or `e`. The structured object is the *first* argument so log processors can index it.\n\n### JIDs — always go through the helpers\n\n```ts\nimport { jidDecode, jidNormalizedUser, areJidsSameUser, isJidUser } from '../WABinary'\n\nconst decoded = jidDecode(rawJid)            // { user, server, device?, agent? } | undefined\nconst normalized = jidNormalizedUser(rawJid) // strips device/agent, lowercases\nconst same = areJidsSameUser(a, b)           // compare user portions only\n```\n\nNever split a JID with `.split('@')` or compare with `===`. JIDs carry device suffixes (`:0`, `:42`), agent fields, and LID/PN duality — string ops will silently miss matches and you'll ship a bug that only fires on multi-device accounts.\n\n### Binary nodes — typed accessors\n\n```ts\nimport { getBinaryNodeChild, getBinaryNodeChildren, getBinaryNodeChildString } from '../WABinary'\n\nconst groupsNode = getBinaryNodeChild(result, 'groups')\nif (!groupsNode) {\n  throw new Boom('missing <groups> in iq response', { statusCode: 502 })\n}\n\nconst groups = getBinaryNodeChildren(groupsNode, 'group') // BinaryNode[]\nconst text = getBinaryNodeChildString(node, 'body')        // string | undefined\nconst { attrs } = node                                     // typed Record<string, string>\n```\n\nDon't reach into `node.content` as an array directly — types are loose and the shape varies by stanza. The accessors handle the missing/single/array cases.\n\n### Sending IQs — `query` with timeouts\n\n```ts\nconst result = await sock.query({\n  tag: 'iq',\n  attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' },\n  content: [{ tag: 'picture', attrs: { type: 'image', query: 'url' } }]\n}, /* timeoutMs */ 15_000)\n```\n\n`query` auto-generates the stanza id, attaches a one-shot listener, and rejects on timeout. Don't write your own `sock.ws.send` + manual listener — you'll leak listeners on errors.\n\n### Listening for incoming stanzas — `CB:` prefix\n\n```ts\nsock.ws.on('CB:ib,,dirty', async (node: BinaryNode) => {\n  const { attrs } = getBinaryNodeChild(node, 'dirty')!\n  // ...\n})\n\nsock.ws.on('CB:notification,type:server_sync', handler)\n```\n\nThe `CB:tag,attr:value` syntax routes by tag + attribute filter inside the websocket. This is how `messages-recv`, `chats`, `groups` listen — don't filter manually inside a generic `'message'` handler.\n\n### Public events — `ev.on`, never call twice\n\n```ts\nsock.ev.on('messages.upsert', ({ messages, type }) => { ... })\nsock.ev.on('connection.update', ({ connection, lastDisconnect }) => { ... })\nsock.ev.on('creds.update', saveCreds)\n```\n\n`saveCreds` (or any handler) must be deduped — registering twice means writing twice. The harness in `__tests__/e2e/helpers/test-client.ts` shows the cleanup pattern (`ev.off` in teardown).\n\n### Async cleanup — bracket pattern\n\nWhen you allocate a resource (timer, listener, ws subscription) inside a Promise, clean it up in *both* paths:\n\n```ts\nreturn new Promise<T>((resolve, reject) => {\n  const timer = setTimeout(() => {\n    cleanup()\n    reject(new Boom('timed out', { statusCode: 408 }))\n  }, timeoutMs)\n\n  const cleanup = () => {\n    clearTimeout(timer)\n    sock.ev.off('event.name', handler)\n  }\n\n  const handler = (data: T) => {\n    if (matches(data)) {\n      cleanup()\n      resolve(data)\n    }\n  }\n\n  sock.ev.on('event.name', handler)\n})\n```\n\nHalf-cleanups are how this codebase grew its memory leaks. The bracket pattern (allocate → cleanup defined → both paths call it) is the fix.\n\n### Imports — `verbatimModuleSyntax`\n\n```ts\nimport type { WAMessage, WAUrlInfo } from '../Types'\nimport { Boom } from '@hapi/boom'\nimport { type BinaryNode, getBinaryNodeChild } from '../WABinary'\n```\n\nType-only imports must be marked `import type` or inline-prefixed `type` — TS strict-mode `verbatimModuleSyntax` will fail the build otherwise. Don't merge a value import with a type import unless you actually use both at runtime.\n\n### Optional chains over null guards\n\n```ts\n// good\nconst text = msg.message?.extendedTextMessage?.text ?? msg.message?.conversation\nif (!sent?.key.id) return\n\n// bad — proliferates `if (x && x.y && x.y.z)` ladders\nif (msg.message && msg.message.extendedTextMessage) { ... }\n```\n\n`noUncheckedIndexedAccess` is on, so array/record indexing returns `T | undefined`. Don't paper over it with `!` unless you have an invariant the type system can't see — and if you do, leave a one-line comment explaining the invariant.\n\n### Tests — colocate, name by behavior\n\n```ts\n// src/__tests__/Utils/decode-wa-message.test.ts\ndescribe('SERVER_ERROR_CODES', () => {\n  it('MessageAccountRestriction is 463', () => {\n    expect(SERVER_ERROR_CODES.MessageAccountRestriction).toBe('463')\n  })\n})\n```\n\nTest files mirror the source path: `src/Foo/bar.ts` → `src/__tests__/Foo/bar.test.ts`. `describe` names the unit, `it` names the behavior in plain English. Avoid `it('works')`.\n\n## Public API discipline\n\n`src/Types/**` and the top-level `src/index.ts` re-exports define the public surface. Treat changes there as breaking unless you can prove additive-only.\n\nFor wire-level changes (`Socket/messages-recv.ts`, `Utils/decode-wa-message.ts`, `WABinary/`, etc.) — describe the WhatsApp-side trigger in the PR. Reviewers can't always reproduce protocol behavior, so the description does the heavy lifting.\n\n## Commits and PRs\n\nConventional commits, scoped where useful:\n\n```\nfeat(socket): add support for reachout limits XWAs\nfix(retry): process <keys> bundle and embed SKDM on resend\nchore(deps): bump ajv from 6.12.6 to 6.15.0\ntest(e2e): test harness + signal/prekey fixes\n```\n\nPR titles follow the same convention. Squash-merge is the default.\n\nBefore opening a PR:\n1. `yarn lint` — must be 0 errors.\n2. `yarn test` — must be all green. If you can't run e2e locally, say so in the PR description.\n3. Don't commit `baileys_auth_info/`, `.env`, `mitm_*.db`, `.superset/`, or any session state. Git is configured to ignore the obvious ones; double-check.\n4. Don't commit regenerated `yarn.lock` from a different package manager. If your `yarn.lock` diff is unexpectedly large (thousands of lines for a one-line `package.json` change), you're using Yarn 1 — switch to Corepack.\n\n## What not to touch without coordination\n\n- **`libsignal` cryptographic flows** — session state, prekeys, sender-key derivation. Subtle bugs here are silent and brick downstream sessions.\n- **`Defaults/baileys-version.json`** — bumped by the `update-version` workflow. Manual edits race with automation.\n- **`WAProto/` generated files** — regenerate, don't hand-edit.\n- **`.github/workflows/`** — CI changes are reviewed separately; bundle them in their own PR when possible.\n- **`package.json` `resolutions`** — these patch known security advisories. Removing entries reintroduces CVEs; check `yarn npm audit --recursive` before pruning.\n\n## Testing expectations\n\n- New public API → unit test in `src/__tests__/`.\n- New protocol path or stanza handler → integration test mocking the binary node, *not* an e2e test (e2e is expensive and flaky in agent loops).\n- New crypto/auth flow → e2e against bartender if feasible, otherwise a deterministic fixture-based unit test.\n\nTests are colocated by area: `src/__tests__/Socket/`, `src/__tests__/Utils/`, `src/__tests__/binary/`. Match the existing layout.\n\n## Security-sensitive changes\n\nIf your change touches:\n- Auth state read/write, key storage, prekey/session lifecycle\n- Message decryption / signature verification\n- Any path that handles user PII (phone numbers, JIDs, message content) in logs or errors\n\n…flag it explicitly in the PR description. See `SECURITY.md` for disclosure of vulnerabilities (do not file them as public issues).\n\n## AI agent etiquette\n\nBeyond the AI policy in `CODE_OF_CONDUCT.md`:\n\n- **Read before you write.** This codebase has subtle protocol invariants. A grep-and-replace agent will make a mess of `Socket/messages-recv.ts`. Read the surrounding handler before editing.\n- **Don't invent WhatsApp protocol details.** If you're not sure how a stanza is structured, find a real example in the tests or in `Utils/decode-wa-message.ts`. Hallucinated XML attributes get merged and then break in production.\n- **Stay in scope.** A bug fix doesn't need a refactor pass. A type tweak doesn't need a comment cleanup PR.\n- **Don't paste auth state into AI tools.** `baileys_auth_info/` contains long-lived Signal keys. Treat it like an SSH private key.\n- **Disclose AI authorship in PRs.** A one-line \"drafted with [tool], reviewed by [human]\" is enough. See `CODE_OF_CONDUCT.md` § AI Policy for the full rule.\n\n## Where to ask\n\n- Discord: https://discord.gg/WeJM5FP9GG\n- Wiki: https://baileys.wiki\n- Security: see `SECURITY.md`\n\nIf you're an agent and you're stuck on something this file doesn't cover, fall back to reading the relevant `src/` directory and the matching tests — they're the source of truth.\n","category":"root","tokens":3476}]}