This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
backendcritical PriorityID: bun-websocket-backpressure-tcp-drain-invariant

Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant

Enforces deterministic backpressure handling in Bun 1.2 native WebSocket servers by evaluating the integer byte return value of `ws.send()`. Failure to inspect send results or handle socket buffer saturation causes silent, unbounded heap accumulation during high-fanout broadcasts, leading to catastrophic OOM termination under slow client conditions.

Prohibited Anti-Pattern: Ignoring `ws.send()` return codes and lacking `drain()` handler causing silent unbounded queue buffering

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant - Enforces deterministic backpressure handling in Bun 1.2 native WebSocket servers by evaluating the integer byte return value of `ws.send()`. Failure to inspect send results or handle socket buffer saturation causes silent, unbounded heap accumulation during high-fanout broadcasts, leading to catastrophic OOM termination under slow client conditions.
globs: src/**/*.{ts,js,mjs}, server/**/*.{ts,js,mjs}, services/**/*.{ts,js,mjs}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/bun-websocket-backpressure-tcp-drain-invariant"
---

# Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Ignoring `ws.send()` return codes and lacking `drain()` handler causing silent unbounded queue buffering

## System Constraints
You are a Principal Systems Engineer enforcing strict backpressure invariants on Bun 1.2 native WebSockets. In Bun, `ws.send(data)` does not return a boolean; it returns an integer representing the byte count sent or queued: `-1` on immediate drop/failure, `0` if dropped due to backpressure, or a positive byte count when queued into native uSockets buffers. You must mandate that all high-throughput broadcast operations inspect `ws.send()`, track backpressure states via `ws.data`, configure explicit per-socket buffer high-water marks, implement the native `drain(ws)` lifecycle hook, and apply shed-load or disconnect policies when clients stall.

## Strict Guidelines
- Engineers MUST ALWAYS inspect the integer return value of `ws.send(data)` to determine the bytes enqueued or if the frame was dropped (return value <= 0).
- Engineers MUST NOT invoke `server.publish()` or `ws.send()` in unbounded loops without backpressure thresholds and slow-client disconnection or queue-eviction strategies.
- Services MUST define the native `drain(ws)` hook in `Bun.serve({ websocket: { drain(ws) { ... } } })` to resume queued streaming once the kernel/uSockets write buffer clears.
- Engineers MUST track pending client backpressure using `ws.data` context, setting a maximum buffer threshold (`maxBackpressureBytes`) before disconnecting misbehaving slow consumers.
- Engineers MUST NEVER assume `server.publish()` inherently handles per-client slow-drain eviction; topic subscribers experiencing backpressure must be isolated or shed.

## Bad vs Good Implementations
### Native Bun 1.2 WebSocket Backpressure with TCP Drain and Slow Client Eviction

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: In high-fanout scenarios, slow consumers cannot drain kernel/uSockets buffers as fast as publishers push. Blindly calling `publish` or `ws.send` without checking return values or configuring backpressure handlers causes Bun's internal memory footprint to explode, resulting in silent process crashes.
```typescript
import { serve } from "bun";

// ANTI-PATTERN: Blindly sending data to all sockets without checking backpressure or implementing drain
serve({
  port: 8080,
  websocket: {
    message(ws, message) {
      // Broadcast blindly to everyone
      // Under high load or slow consumers, native uSockets buffers grow silently until OOM
      ws.publish("chat", message);
    }
  },
  fetch(req, server) {
    if (server.upgrade(req)) return;
    return new Response("Upgrade failed", { status: 400 });
  }
});
```

#### Production Standard (Correct)
> **Standard**: Implements explicit backpressure controls: configures `backpressureLimit` and `closeOnBackpressureLimit` in Bun's native WebSocket options, evaluates `ws.send()` return codes, stores pending frames in a bounded queue, and utilizes the `drain` hook to flush queued packets when the kernel socket becomes writable again.
```typescript
import { serve, type ServerWebSocket } from "bun";

interface SocketData {
  id: string;
  bufferedQueue: Uint8Array[];
  isBackpressured: boolean;
}

const MAX_BUFFERED_BYTES = 1024 * 1024; // 1MB threshold before dropping slow client

serve<SocketData>({
  port: 8080,
  websocket: {
    // In Bun 1.2, backpressure limit triggers dropping or queueing
    backpressureLimit: MAX_BUFFERED_BYTES,
    closeOnBackpressureLimit: true,
    
    open(ws: ServerWebSocket<SocketData>) {
      ws.data = {
        id: crypto.randomUUID(),
        bufferedQueue: [],
        isBackpressured: false,
      };
    },

    message(ws: ServerWebSocket<SocketData>, message) {
      const payload = typeof message === "string" ? new TextEncoder().encode(message) : message;
      
      // Safe send checking returned byte count
      if (ws.data.isBackpressured) {
        ws.data.bufferedQueue.push(new Uint8Array(payload));
        return;
      }

      const sentBytes = ws.send(payload);
      if (sentBytes === 0 || sentBytes === -1) {
        // Socket buffer full or dropped; engage backpressure mode
        ws.data.isBackpressured = true;
        ws.data.bufferedQueue.push(new Uint8Array(payload));
      }
    },

    // Mandatory invariant: drain hook handles flushes once the TCP socket drains
    drain(ws: ServerWebSocket<SocketData>) {
      while (ws.data.bufferedQueue.length > 0) {
        const next = ws.data.bufferedQueue[0];
        const sentBytes = ws.send(next);
        
        if (sentBytes > 0) {
          ws.data.bufferedQueue.shift();
        } else {
          // Socket remains saturated, await next drain event
          return;
        }
      }
      ws.data.isBackpressured = false;
    },

    close(ws: ServerWebSocket<SocketData>) {
      ws.data.bufferedQueue.length = 0;
    }
  },
  fetch(req, server) {
    if (server.upgrade(req, { data: { id: "", bufferedQueue: [], isBackpressured: false } })) {
      return;
    }
    return new Response("Upgrade failed", { status: 400 });
  }
});
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant

Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant defines an industrial-grade behavioral standard for backend engineering. Enforces deterministic backpressure handling in Bun 1.2 native WebSocket servers by evaluating the integer byte return value of `ws.send()`. Failure to inspect send results or handle socket buffer saturation causes silent, unbounded heap accumulation during high-fanout broadcasts, leading to catastrophic OOM termination under slow client conditions. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Ignoring `ws.send()` return codes and lacking `drain()` handler causing silent unbounded queue buffering Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftIgnoring `ws.send()` return codes and lacking `drain()` handler causing silent unbounded queue bufferingCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS inspect the integer return value of `ws.send(data)` to determine the bytes enqueued or if the frame was dropped (return value <= 0).

Engineers MUST ALWAYS inspect the integer return value of `ws.send(data)` to determine the bytes enqueued or if the frame was dropped (return value <= 0).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT invoke `server.publish()` or `ws.send()` in unbounded loops without backpressure thresholds and slow-client disconnection or queue-eviction strategies.

Engineers MUST NOT invoke `server.publish()` or `ws.send()` in unbounded loops without backpressure thresholds and slow-client disconnection or queue-eviction strategies.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Services MUST define the native `drain(ws)` hook in `Bun.serve({ websocket

Services MUST define the native `drain(ws)` hook in `Bun.serve({ websocket: { drain(ws) { ... } } })` to resume queued streaming once the kernel/uSockets write buffer clears.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Engineers MUST track pending client backpressure using `ws.data` context, setting a maximum buffer threshold (`maxBackpressureBytes`) before disconnecting misbehaving slow consumers.

Engineers MUST track pending client backpressure using `ws.data` context, setting a maximum buffer threshold (`maxBackpressureBytes`) before disconnecting misbehaving slow consumers.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Engineers MUST NEVER assume `server.publish()` inherently handles per-client slow-drain eviction

Engineers MUST NEVER assume `server.publish()` inherently handles per-client slow-drain eviction; topic subscribers experiencing backpressure must be isolated or shed.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Bun 1.2 Native WebSocket Backpressure and TCP Drain Invariant critical for AI coding assistants?

Enforces deterministic backpressure handling in Bun 1.2 native WebSocket servers by evaluating the integer byte return value of `ws.send()`. Failure to inspect send results or handle socket buffer saturation causes silent, unbounded heap accumulation during high-fanout broadcasts, leading to catastrophic OOM termination under slow client conditions. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Ignoring `ws.send()` return codes and lacking `drain()` handler causing silent unbounded queue buffering) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

1 Rule in Stack

Bun