debugging-websocket-issues
Use when seeing WebSocket errors like "Invalid frame header", "RSV1 must be clear", or "WS_ERR_UNEXPECTED_RSV_1" - covers multiple WebSocketServer conflicts, compression issues, and raw frame debugging techniques
Best use case
debugging-websocket-issues is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use when seeing WebSocket errors like "Invalid frame header", "RSV1 must be clear", or "WS_ERR_UNEXPECTED_RSV_1" - covers multiple WebSocketServer conflicts, compression issues, and raw frame debugging techniques
Teams using debugging-websocket-issues should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/debugging-websocket-issues/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How debugging-websocket-issues Compares
| Feature / Agent | debugging-websocket-issues | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use when seeing WebSocket errors like "Invalid frame header", "RSV1 must be clear", or "WS_ERR_UNEXPECTED_RSV_1" - covers multiple WebSocketServer conflicts, compression issues, and raw frame debugging techniques
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Debugging WebSocket Issues
## Overview
WebSocket "invalid frame header" errors often stem from raw HTTP being written to an upgraded socket, not actual frame corruption. The most common cause is multiple `WebSocketServer` instances conflicting on the same HTTP server.
## When to Use
- Error: `Invalid WebSocket frame: RSV1 must be clear`
- Error: `WS_ERR_UNEXPECTED_RSV_1`
- Error: `Invalid frame header`
- WebSocket connects then immediately disconnects with code 1006
- Server logs success but client receives garbage data
## Quick Reference
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| RSV1 must be clear | Multiple WSS on same server OR compression mismatch | Use `noServer: true` mode |
| Hex starts with `48545450` | Raw HTTP on WebSocket (0x48='H') | Check for conflicting upgrade handlers |
| Code 1006, no reason | Abnormal closure, often server-side abort | Check `abortHandshake` calls |
| Works isolated, fails in app | Something else writing to socket | Audit all upgrade listeners |
## The Multiple WebSocketServer Bug
### Problem
When attaching multiple `WebSocketServer` instances to the same HTTP server using the `server` option:
```typescript
// ❌ BAD - Both servers add upgrade listeners, causing conflicts
const wss1 = new WebSocketServer({ server, path: '/ws' });
const wss2 = new WebSocketServer({ server, path: '/ws/other' });
```
**What happens:**
1. Client connects to `/ws`
2. BOTH upgrade handlers fire (Node.js EventEmitter calls all listeners)
3. `wss1` matches path, handles upgrade successfully
4. `wss2` doesn't match, calls `abortHandshake(socket, 400)`
5. Raw `HTTP/1.1 400 Bad Request` written to the now-WebSocket socket
6. Client receives HTTP text as WebSocket frame data
7. First byte `0x48` ('H') interpreted as: RSV1=1, opcode=8 → invalid frame
### Solution
Use `noServer: true` and manually route upgrades:
```typescript
// ✅ GOOD - Single upgrade handler routes to correct server
const wss1 = new WebSocketServer({ noServer: true, perMessageDeflate: false });
const wss2 = new WebSocketServer({ noServer: true, perMessageDeflate: false });
server.on('upgrade', (request, socket, head) => {
const pathname = new URL(request.url || '', `http://${request.headers.host}`).pathname;
if (pathname === '/ws') {
wss1.handleUpgrade(request, socket, head, (ws) => {
wss1.emit('connection', ws, request);
});
} else if (pathname === '/ws/other') {
wss2.handleUpgrade(request, socket, head, (ws) => {
wss2.emit('connection', ws, request);
});
} else {
socket.destroy();
}
});
```
## Debugging Techniques
### Raw Frame Inspection
Hook into the socket to see actual bytes received:
```typescript
ws.on('open', () => {
const socket = ws._socket;
const originalPush = socket.push.bind(socket);
socket.push = function(chunk, encoding) {
if (chunk) {
console.log('First 20 bytes (hex):', chunk.slice(0, 20).toString('hex'));
const byte0 = chunk[0];
console.log(`FIN: ${!!(byte0 & 0x80)}, RSV1: ${!!(byte0 & 0x40)}, Opcode: ${byte0 & 0x0f}`);
// Check if it's actually HTTP text
if (chunk.slice(0, 4).toString() === 'HTTP') {
console.log('*** RECEIVED RAW HTTP ON WEBSOCKET ***');
}
}
return originalPush(chunk, encoding);
};
});
```
### Key Hex Patterns
- `81` = FIN + text frame (normal)
- `82` = FIN + binary frame (normal)
- `88` = FIN + close frame (normal)
- `48545450` = "HTTP" - raw HTTP on WebSocket (bug!)
- `c1` or similar with bit 6 set = compressed frame (RSV1=1)
## Common Mistakes
| Mistake | Result | Fix |
|---------|--------|-----|
| Multiple WSS with `server` option | HTTP 400 written to socket | Use `noServer: true` |
| `perMessageDeflate: true` (default in older ws) | RSV1 set on frames | Explicitly set `perMessageDeflate: false` |
| Not checking upgrade headers | Miss compression negotiation | Log `sec-websocket-extensions` header |
| Assuming RSV1 error = compression | Could be raw HTTP | Check if bytes decode as ASCII "HTTP" |
## Verification Checklist
After fixing, verify:
- [ ] `RSV1: false` in frame inspection
- [ ] `Extensions header: NONE` in upgrade response
- [ ] No `HTTP/1.1` in raw frame data
- [ ] Messages received match sent payload size
- [ ] Multiple broadcasts work (test interval sends)Related Skills
agent-relay-orchestrator
Run headless multi-agent orchestration sessions via Agent Relay. Use when spawning teams of agents, creating channels for coordination, managing agent lifecycle, and running parallel workloads across Claude/Codex/Gemini/Pi/Droid agents.
adding-swarm-patterns
Use when adding new multi-agent coordination patterns to agent-relay - provides checklist for types, schema, templates, and docs updates
agent-relay
Use when you need Codex to coordinate multiple agents through Relaycast for peer-to-peer messaging, lead/worker handoffs, or shared status tracking across sub-agents and terminals.
openclaw-relay
Real-time messaging across OpenClaw instances (channels, DMs, threads, reactions, search).
prpm-json-best-practices
Best practices for structuring prpm.json package manifests with required fields, tags, organization, multi-package management, enhanced file format, eager/lazy activation, and conversion hints
implementing-command-palettes
Use when building Cmd+K command palettes in React - covers keyboard navigation with arrow keys, keeping selected items in view with scrollIntoView, filtering with shortcut matching, and preventing infinite re-renders from reference instability
github-oauth-nango-integration
Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
deploying-to-staging-environment
Use when deploying changes to staging across relay, relay-dashboard, and relay-cloud repos - coordinates multi-repo branch syncing using git worktrees, automatically triggers staging deployments via GitHub Actions
creating-skills
Use when creating new Claude Code skills or improving existing ones - ensures skills are discoverable, scannable, and effective through proper structure, CSO optimization, and real examples
creating-claude-rules
Use when creating or fixing .claude/rules/ files - provides correct paths frontmatter (not globs), glob patterns, and avoids Cursor-specific fields like alwaysApply
creating-claude-hooks
Use when creating or publishing Claude Code hooks - covers executable format, event types, JSON I/O, exit codes, security requirements, and PRPM package structure