debugging-websocket-issues
WebSocket通信で「無効なフレームヘッダー」などのエラーが発生した場合に、サーバーの競合や圧縮の問題を特定し、フレームを直接確認して原因を突き止める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
🇯🇵 日本人クリエイター向け解説
WebSocket通信で「無効なフレームヘッダー」などのエラーが発生した場合に、サーバーの競合や圧縮の問題を特定し、フレームを直接確認して原因を突き止めるSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o debugging-websocket-issues.zip https://jpskill.com/download/17076.zip && unzip -o debugging-websocket-issues.zip && rm debugging-websocket-issues.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17076.zip -OutFile "$d\debugging-websocket-issues.zip"; Expand-Archive "$d\debugging-websocket-issues.zip" -DestinationPath $d -Force; ri "$d\debugging-websocket-issues.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
debugging-websocket-issues.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
debugging-websocket-issuesフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
WebSocketの問題のデバッグ
概要
WebSocketの「invalid frame header」エラーは、多くの場合、実際のフレームの破損ではなく、アップグレードされたソケットに生のHTTPが書き込まれることに起因します。最も一般的な原因は、複数の WebSocketServer インスタンスが同じHTTPサーバー上で競合することです。
使用するタイミング
- エラー:
Invalid WebSocket frame: RSV1 must be clear - エラー:
WS_ERR_UNEXPECTED_RSV_1 - エラー:
Invalid frame header - WebSocketが接続後、コード1006ですぐに切断される
- サーバーのログは成功しているが、クライアントがガベージデータを受信する
クイックリファレンス
| 症状 | 考えられる原因 | 解決策 |
|---|---|---|
| RSV1 must be clear | 同じサーバー上に複数のWSSがある、または圧縮の不一致 | noServer: true モードを使用する |
Hexが 48545450 で始まる |
WebSocket上の生のHTTP (0x48='H') | 競合するアップグレードハンドラーを確認する |
| コード1006、理由なし | 異常なクローズ、多くの場合サーバー側の異常終了 | abortHandshake の呼び出しを確認する |
| 単独では動作するが、アプリでは失敗する | 他の何かがソケットに書き込んでいる | すべてのアップグレードリスナーを監査する |
複数のWebSocketServerのバグ
問題
server オプションを使用して、複数の WebSocketServer インスタンスを同じHTTPサーバーにアタッチする場合:
// ❌ BAD - 両方のサーバーがアップグレードリスナーを追加し、競合を引き起こす
const wss1 = new WebSocketServer({ server, path: '/ws' });
const wss2 = new WebSocketServer({ server, path: '/ws/other' });
何が起こるか:
- クライアントが
/wsに接続する - 両方のアップグレードハンドラーが発火する (Node.js EventEmitterはすべてのリスナーを呼び出す)
wss1がパスに一致し、アップグレードを正常に処理するwss2が一致せず、abortHandshake(socket, 400)を呼び出す- 生の
HTTP/1.1 400 Bad RequestがWebSocketソケットに書き込まれる - クライアントがHTTPテキストをWebSocketフレームデータとして受信する
- 最初のバイト
0x48('H') が次のように解釈される: RSV1=1, opcode=8 → 無効なフレーム
解決策
noServer: true を使用し、手動でアップグレードをルーティングする:
// ✅ GOOD - 単一のアップグレードハンドラーが正しいサーバーにルーティングする
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();
}
});
デバッグテクニック
生のフレームの検査
ソケットにフックして、実際に受信したバイトを確認する:
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);
};
});
主要な16進数のパターン
81= FIN + テキストフレーム (正常)82= FIN + バイナリフレーム (正常)88= FIN + クローズフレーム (正常)48545450= "HTTP" - WebSocket上の生のHTTP (バグ!)c1またはビット6が設定された同様のもの = 圧縮されたフレーム (RSV1=1)
よくある間違い
| 間違い | 結果 | 解決策 |
|---|---|---|
server オプションを持つ複数のWSS |
HTTP 400 がソケットに書き込まれる | noServer: true を使用する |
perMessageDeflate: true (古い ws のデフォルト) |
フレームに RSV1 が設定される | 明示的に perMessageDeflate: false を設定する |
| アップグレードヘッダーをチェックしない | 圧縮ネゴシエーションを見逃す | sec-websocket-extensions ヘッダーをログに記録する |
| RSV1 エラー = 圧縮と仮定する | 生のHTTPの可能性がある | バイトがASCII "HTTP"としてデコードされるかどうかを確認する |
検証チェックリスト
修正後、以下を確認する:
- [ ] フレーム検査で
RSV1: falseであること - [ ] アップグレード応答で
Extensions header: NONEであること - [ ] 生のフレームデータに
HTTP/1.1がないこと - [ ] 受信したメッセージが送信されたペイロードサイズと一致すること
- [ ] 複数のブロードキャストが動作すること (テスト間隔送信)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
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:
// ❌ 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:
- Client connects to
/ws - BOTH upgrade handlers fire (Node.js EventEmitter calls all listeners)
wss1matches path, handles upgrade successfullywss2doesn't match, callsabortHandshake(socket, 400)- Raw
HTTP/1.1 400 Bad Requestwritten to the now-WebSocket socket - Client receives HTTP text as WebSocket frame data
- First byte
0x48('H') interpreted as: RSV1=1, opcode=8 → invalid frame
Solution
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();
}
});
Debugging Techniques
Raw Frame Inspection
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);
};
});
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!)c1or 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: falsein frame inspection - [ ]
Extensions header: NONEin upgrade response - [ ] No
HTTP/1.1in raw frame data - [ ] Messages received match sent payload size
- [ ] Multiple broadcasts work (test interval sends)