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

Bun Native WebSocket Backpressure Drain & High-Watermark Guardrail

Enforces strict high-watermark backpressure checks and drain-driven event dispatching on Bun native WebSockets (`Bun.serve`). Unchecked streaming without inspecting `ws.send()` byte returns or awaiting the `drain(ws)` lifecycle hook creates unmonitored native uWebSockets heap allocations that lead to silent process termination via OOM.

Prohibited Anti-Pattern: Fire-and-forget WebSocket streaming without checking send return values or handling native drain lifecycle events.

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Bun Native WebSocket Backpressure Drain & High-Watermark Guardrail - Enforces strict high-watermark backpressure checks and drain-driven event dispatching on Bun native WebSockets (`Bun.serve`). Unchecked streaming without inspecting `ws.send()` byte returns or awaiting the `drain(ws)` lifecycle hook creates unmonitored native uWebSockets heap allocations that lead to silent process termination via OOM.
globs: src/**/*.{ts,tsx}, server/**/*.{ts,tsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/bun-websocket-backpressure-drain-invariant-bun-1-2-native-w-975"
---

# Bun Native WebSocket Backpressure Drain & High-Watermark Guardrail

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Fire-and-forget WebSocket streaming without checking send return values or handling native drain lifecycle events.

## System Constraints
You are a Principal Backend Systems Architect enforcing the Bun 1.2 Native WebSocket Backpressure Invariant. In Bun, `ws.send()` does not return a Promise; it returns a number indicating byte write status (0 = dropped/closed, -1 = queued internally, or positive byte count written). When internal buffers fill up, native memory explodes if writes continue. You must enforce explicit backpressure checks: tracking `ws.isBackpressured`, caching outbound frames in managed queues with hard ceiling bounds, and resuming dispatch strictly inside the `drain(ws)` socket handler.

## Strict Guidelines
- Engineers MUST ALWAYS inspect the numeric return value of `ws.send(data)` or evaluate `ws.isBackpressured` prior to continuing high-throughput frame emission.
- Engineers MUST NOT rely on unthrottled loops (`for`, `while`, unbounded EventEmitter listeners) to pump binary or text frames directly into `ws.send()`.
- Engineers MUST implement the `drain(ws)` callback in the `Bun.serve` WebSocket options and resume pending message queues exclusively when the drain event triggers.
- Services MUST define a deterministic max-buffered-amount threshold (high watermark); if a client's queued frames exceed this threshold, the server MUST terminate the socket with status code 1008/1011 to protect the host process.
- Components NEVER store unbounded arrays in JavaScript memory to compensate for backpressure without a hard ceiling eviction or disconnect policy.

## Bad vs Good Implementations
### High-Throughput WebSocket Streaming with Native Drain Invariant

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Bun's `ws.send()` operates on native uWebSockets buffers. Pumping data continuously without evaluating backpressure causes native memory to grow unbounded outside the V8/JSC garbage-collected heap, evading traditional heap profiling until the host crashes.
```typescript
import { serve, type ServerWebSocket } from 'bun';

interface ClientData {
  id: string;
}

serve<ClientData>({
  port: 8080,
  websocket: {
    open(ws: ServerWebSocket<ClientData>) {
      // CRITICAL BUG: Pushing frames in an unconstrained loop without
      // inspecting ws.send() returns or ws.isBackpressured. Causes rapid native OOM.
      const interval = setInterval(() => {
        const telemetry = crypto.getRandomValues(new Uint8Array(4096));
        ws.send(telemetry);
      }, 1);
    },
    message(ws, msg) {},
    close(ws) {}
  },
  fetch(req, server) {
    if (server.upgrade(req)) return;
    return new Response('Upgrade failed', { status: 400 });
  }
});
```

#### Production Standard (Correct)
> **Standard**: Implements an explicit high-watermark check, buffers outgoing frames cleanly when `ws.isBackpressured` is true, reschedules flushing exclusively via the `drain(ws)` socket hook, and aggressively evicts clients exceeding `MAX_BUFFERED_BYTES` to prevent memory exhaustion.
```typescript
import { serve, type ServerWebSocket } from 'bun';

interface SocketState {
  id: string;
  isPaused: boolean;
  queue: Uint8Array[];
  queuedBytes: number;
}

const MAX_BUFFERED_BYTES = 4 * 1024 * 1024; // 4MB Hard ceiling per client

function flushQueue(ws: ServerWebSocket<SocketState>): void {
  while (ws.data.queue.length > 0) {
    if (ws.isBackpressured) {
      ws.data.isPaused = true;
      return;
    }
    const frame = ws.data.queue.shift()!;
    ws.data.queuedBytes -= frame.byteLength;
    const result = ws.send(frame);
    if (result === -1) {
      // Frame queued in native uWebSockets buffer; backpressure threshold reached
      ws.data.isPaused = true;
      return;
    }
  }
  ws.data.isPaused = false;
}

serve<SocketState>({
  port: 8080,
  websocket: {
    open(ws: ServerWebSocket<SocketState>) {
      ws.data = { id: crypto.randomUUID(), isPaused: false, queue: [], queuedBytes: 0 };
    },
    drain(ws: ServerWebSocket<SocketState>) {
      // Native buffer has drained below low watermark; resume dispatch
      flushQueue(ws);
    },
    message(ws, message) {},
    close(ws) {
      ws.data.queue.length = 0;
      ws.data.queuedBytes = 0;
    }
  },
  fetch(req, server) {
    const success = server.upgrade(req, {
      data: { id: '', isPaused: false, queue: [], queuedBytes: 0 }
    });
    return success ? undefined : new Response('WebSocket upgrade failed', { status: 400 });
  }
});

export function enqueueEvent(ws: ServerWebSocket<SocketState>, payload: Uint8Array): void {
  if (ws.data.queuedBytes + payload.byteLength > MAX_BUFFERED_BYTES) {
    // Terminate misbehaving slow consumer to preserve system stability
    ws.close(1008, 'Buffer overflow: Client consuming too slowly');
    return;
  }

  ws.data.queue.push(payload);
  ws.data.queuedBytes += payload.byteLength;

  if (!ws.data.isPaused) {
    flushQueue(ws);
  }
}
```

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 Native WebSocket Backpressure Drain & High-Watermark Guardrail

Bun Native WebSocket Backpressure Drain & High-Watermark Guardrail defines an industrial-grade behavioral standard for backend engineering. Enforces strict high-watermark backpressure checks and drain-driven event dispatching on Bun native WebSockets (`Bun.serve`). Unchecked streaming without inspecting `ws.send()` byte returns or awaiting the `drain(ws)` lifecycle hook creates unmonitored native uWebSockets heap allocations that lead to silent process termination via OOM. 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: Fire-and-forget WebSocket streaming without checking send return values or handling native drain lifecycle events. 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 DriftFire-and-forget WebSocket streaming without checking send return values or handling native drain lifecycle events.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 inspect the numeric return value of `ws.send(data)` or evaluate `ws.isBackpressured` prior to continuing high-throughput frame emission.

Engineers MUST ALWAYS inspect the numeric return value of `ws.send(data)` or evaluate `ws.isBackpressured` prior to continuing high-throughput frame emission.

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 rely on unthrottled loops (`for`, `while`, unbounded EventEmitter listeners) to pump binary or text frames directly into `ws.send()`.

Engineers MUST NOT rely on unthrottled loops (`for`, `while`, unbounded EventEmitter listeners) to pump binary or text frames directly into `ws.send()`.

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 implement the `drain(ws)` callback in the `Bun.serve` WebSocket options and resume pending message queues exclusively when the drain event triggers.

Engineers MUST implement the `drain(ws)` callback in the `Bun.serve` WebSocket options and resume pending message queues exclusively when the drain event triggers.

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

Services MUST define a deterministic max-buffered-amount threshold (high watermark)

Services MUST define a deterministic max-buffered-amount threshold (high watermark); if a client's queued frames exceed this threshold, the server MUST terminate the socket with status code 1008/1011 to protect the host process.

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

Components NEVER store unbounded arrays in JavaScript memory to compensate for backpressure without a hard ceiling eviction or disconnect policy.

Components NEVER store unbounded arrays in JavaScript memory to compensate for backpressure without a hard ceiling eviction or disconnect policy.

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 Native WebSocket Backpressure Drain & High-Watermark Guardrail critical for AI coding assistants?

Enforces strict high-watermark backpressure checks and drain-driven event dispatching on Bun native WebSockets (`Bun.serve`). Unchecked streaming without inspecting `ws.send()` byte returns or awaiting the `drain(ws)` lifecycle hook creates unmonitored native uWebSockets heap allocations that lead to silent process termination via OOM. 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 (Fire-and-forget WebSocket streaming without checking send return values or handling native drain lifecycle events.) 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