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

Bun 1.2 Native WebSocket Backpressure & Drain Guardrail

Enforces strict backpressure monitoring and zero-allocation drain lifecycle handling on Bun native WebSockets. Mandates buffer bounds checking and consumer eviction via getBufferedAmount() to prevent edge runtime OOM crashes during asymmetric pub/sub fan-outs.

Prohibited Anti-Pattern: Blind socket fan-out broadcasting without drain handlers or buffered amount limits, causing unbounded buffer accumulation and SIGKILL OOMs.

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Bun 1.2 Native WebSocket Backpressure & Drain Guardrail - Enforces strict backpressure monitoring and zero-allocation drain lifecycle handling on Bun native WebSockets. Mandates buffer bounds checking and consumer eviction via getBufferedAmount() to prevent edge runtime OOM crashes during asymmetric pub/sub fan-outs.
globs: src/**/*.{ts,js}, server/**/*.{ts,js}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/bun-websocket-backpressure-drain-guardrail"
---

# Bun 1.2 Native WebSocket Backpressure & Drain Guardrail

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Blind socket fan-out broadcasting without drain handlers or buffered amount limits, causing unbounded buffer accumulation and SIGKILL OOMs.

## System Constraints
You are a Principal Systems Engineer enforcing Bun 1.2 Native WebSocket Backpressure & Drain invariants. In high-throughput pub/sub architectures, writing to slow WebSocket clients without checking socket backpressure causes Bun's internal C++ ring buffers to expand indefinitely until the OS issues a SIGKILL. You must ensure all WebSocket handlers implement bounded backpressure limits, zero-allocation drain callbacks, and defensive slow-client termination.

## Strict Guidelines
- Engineers MUST ALWAYS verify ws.getBufferedAmount() before calling ws.send() on high-frequency streams.
- Engineers MUST implement the native drain(ws) lifecycle hook in Bun.serve() whenever buffered messages are queued.
- Engineers MUST set a hard ceiling (MAX_BUFFERED_BYTES, typically 512KB to 1MB) and immediately evict or throttle clients exceeding this threshold using ws.close(1008, 'Policy Violation: Buffer Overflow').
- Code MUST NOT allocate intermediate closures, unbounded array slices, or object wrappers inside the drain(ws) callback path.
- Services MUST prefer native server.publish(topic, data) for zero-copy fan-out, but MUST maintain per-client state checks for targeted or stateful binary streams.
- Components NEVER store unbounded arrays of unacknowledged messages directly on ws.data without a fixed-capacity circular ring buffer or drop strategy.

## Bad vs Good Implementations
### Unbounded WebSocket Fan-Out vs. Backpressure-Aware Zero-Allocation Drain

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The bad example accumulates messages without checking buffer limits or socket transmission results. Under slow client conditions, calling ws.send() continuously forces Bun's internal C++ write buffers to balloon without bound. Missing a drain() handler means the runtime cannot signal when the kernel socket buffer has emptied, leading to unrecoverable process termination via SIGKILL.
```typescript
import { serve } from 'bun';

interface ClientData {
  id: string;
  queue: string[];
}

serve<ClientData>({
  port: 3000,
  fetch(req, server) {
    server.upgrade(req, { data: { id: crypto.randomUUID(), queue: [] } });
    return undefined;
  },
  websocket: {
    message(ws, message) {
      // CRITICAL: Blindly pushing into an unbounded array and sending
      // ignores client read capacity, leading to rapid process OOM under high broadcast load
      ws.data.queue.push(String(message));
      for (const item of ws.data.queue) {
        ws.send(item);
      }
      ws.data.queue = [];
    },
    // CRITICAL: Missing drain(ws) handler entirely
  },
});
```

#### Production Standard (Correct)
> **Standard**: The good example queries ws.getBufferedAmount() before sending, enforces a strict 512KB backpressure threshold, and closes non-responsive slow clients using standard WebSocket close code 1008. It registers a zero-allocation drain() hook that resumes transmission using a single pre-allocated reference on ws.data without creating garbage collector pressure.
```typescript
import { serve, type ServerWebSocket } from 'bun';

const MAX_BUFFERED_BYTES = 512 * 1024; // 512 KB hard limit per client

interface ClientData {
  id: string;
  pendingChunk: Uint8Array | null;
}

serve<ClientData>({
  port: 3000,
  fetch(req, server) {
    const upgraded = server.upgrade(req, {
      data: { id: crypto.randomUUID(), pendingChunk: null },
    });
    return upgraded ? undefined : new Response('Upgrade Failed', { status: 400 });
  },
  websocket: {
    // Backpressure drain callback: Zero-allocation resumption
    drain(ws: ServerWebSocket<ClientData>) {
      const pending = ws.data.pendingChunk;
      if (!pending) return;

      // Attempt to push remainder only if buffer has cleared
      if (ws.getBufferedAmount() < MAX_BUFFERED_BYTES / 2) {
        const sentBytes = ws.send(pending);
        if (sentBytes > 0) {
          ws.data.pendingChunk = null;
        }
      }
    },
    message(ws: ServerWebSocket<ClientData>, message) {
      const payload = typeof message === 'string' ? Buffer.from(message) : (message as Uint8Array);
      const currentBuffered = ws.getBufferedAmount();

      // Enforce hard upper bound: Terminate slow/malicious consumers
      if (currentBuffered + payload.byteLength > MAX_BUFFERED_BYTES) {
        ws.close(1008, 'Policy Violation: Backpressure Exceeded');
        return;
      }

      // If buffer is already backpressured, hold at most one contiguous chunk and await drain
      if (currentBuffered > 0) {
        ws.data.pendingChunk = payload;
        return;
      }

      const sentBytes = ws.send(payload);
      if (sentBytes === 0) {
        // Socket accepted 0 bytes into kernel; queue chunk for drain
        ws.data.pendingChunk = payload;
      }
    },
    close(ws: ServerWebSocket<ClientData>) {
      ws.data.pendingChunk = null;
    },
  },
});
```

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 & Drain Guardrail

Bun 1.2 Native WebSocket Backpressure & Drain Guardrail defines an industrial-grade behavioral standard for backend engineering. Enforces strict backpressure monitoring and zero-allocation drain lifecycle handling on Bun native WebSockets. Mandates buffer bounds checking and consumer eviction via getBufferedAmount() to prevent edge runtime OOM crashes during asymmetric pub/sub fan-outs. 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: Blind socket fan-out broadcasting without drain handlers or buffered amount limits, causing unbounded buffer accumulation and SIGKILL OOMs. 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 DriftBlind socket fan-out broadcasting without drain handlers or buffered amount limits, causing unbounded buffer accumulation and SIGKILL OOMs.Critical
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 verify ws.getBufferedAmount() before calling ws.send() on high-frequency streams.

Engineers MUST ALWAYS verify ws.getBufferedAmount() before calling ws.send() on high-frequency streams.

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 implement the native drain(ws) lifecycle hook in Bun.serve() whenever buffered messages are queued.

Engineers MUST implement the native drain(ws) lifecycle hook in Bun.serve() whenever buffered messages are queued.

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

Engineers MUST set a hard ceiling (MAX_BUFFERED_BYTES, typically 512KB to 1MB) and immediately evict or throttle clients exceeding this threshold using ws.close(1008, 'Policy Violation

Engineers MUST set a hard ceiling (MAX_BUFFERED_BYTES, typically 512KB to 1MB) and immediately evict or throttle clients exceeding this threshold using ws.close(1008, 'Policy Violation: Buffer Overflow').

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

Code MUST NOT allocate intermediate closures, unbounded array slices, or object wrappers inside the drain(ws) callback path.

Code MUST NOT allocate intermediate closures, unbounded array slices, or object wrappers inside the drain(ws) callback path.

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

Services MUST prefer native server.publish(topic, data) for zero-copy fan-out, but MUST maintain per-client state checks for targeted or stateful binary streams.

Services MUST prefer native server.publish(topic, data) for zero-copy fan-out, but MUST maintain per-client state checks for targeted or stateful binary streams.

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.
6

Components NEVER store unbounded arrays of unacknowledged messages directly on ws.data without a fixed-capacity circular ring buffer or drop strategy.

Components NEVER store unbounded arrays of unacknowledged messages directly on ws.data without a fixed-capacity circular ring buffer or drop strategy.

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 & Drain Guardrail critical for AI coding assistants?

Enforces strict backpressure monitoring and zero-allocation drain lifecycle handling on Bun native WebSockets. Mandates buffer bounds checking and consumer eviction via getBufferedAmount() to prevent edge runtime OOM crashes during asymmetric pub/sub fan-outs. 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 (Blind socket fan-out broadcasting without drain handlers or buffered amount limits, causing unbounded buffer accumulation and SIGKILL OOMs.) 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