browser-testing-with-s…
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and screenshot capture for confirming UI…
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
$ npx -y skills add AgentWorkforce/relay --skill debugging-websocket-issues --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/debugging-websocket-issuesContext preview
The summary Claude sees to decide when to auto-load this skill.
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
name: debugging-websocket-issues description: 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 tags: websocket, debugging, ws, node
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.
| 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 |
When attaching multiple `WebSocketServer` instances to the same HTTP server using the `server` option:
// ❌ 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
Use `noServer: true` and manually route upgrades:
// ✅ 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();
}
});Hook into the socket to see actual bytes received:
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);
};
});| 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" |
After fixing, verify:
Let Claude Code message Codex. Let your Hyperagent talk to your Hermes agent. Give your custom agents a way to message each other.
Repo: AgentWorkforce/relay
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and screenshot capture for confirming UI…
Use when coordinating multiple AI agents with Agent Relay's workflow engine and need to pick the right orchestration pattern - covers the 10 core patterns…
Use when creating or improving Claude Code agents. Expert guidance on agent file structure, frontmatter, persona definition, tool access, model selection, and…
Use when creating or publishing Claude Code hooks - covers executable format, event types, JSON I/O, exit codes, security requirements, and PRPM package…
Use when creating or fixing .claude/rules/ files - provides correct paths frontmatter (not globs), glob patterns, and avoids Cursor-specific fields like…
Use when creating new Claude Code skills or improving existing ones - ensures skills are discoverable, scannable, and effective through proper structure, CSO…