OpenClaw コントロール UI の非アクティブ後に HTTP 401 認証エラー - Web UI Session Timeout: HTTP 401 After Inactivity in OpenClaw Control UI
OpenClaw コントロール UI は、WebSocket セッションの有効期限切れにより、再接続時の自動トークン再認証がないため、非アクティブ状態から 10〜15 分後に HTTP 401 無効な認証を返します。
🔍 症状
主な症状
ブラウザタブが10〜15分間非アクティブになった後、OpenClaw Control UIからメッセージを送信しようとすると、認証エラーが発生します:
HTTP 401: Invalid Authentication
Status: 401 Unauthorized
X-Error-Code: INVALID_AUTH_TOKEN
再現手順
- ブラウザで
http://127.0.0.1:18789/にアクセスする - ゲートウェイトークンで認証する(自動的に
localStorageに保存される) - WebSocket接続で2〜3件のメッセージを正常に送信する
- タブを10〜15分間非アクティブのままにする(閉じない)
- タブに戻り、新しいメッセージを送信しようとする
- UIとネットワークコンソールで
401エラーを確認する
障害発生時のネットワークアクティビティ
ブラウザ DevTools(F12 → Networkタブ)を確認すると、次のことがわかります:
# WebSocket接続状態
Connection State: CONNECTING → CLOSED
Close Code: 1006 (Abnormal Closure)
Close Reason: "Session expired"
# 後続のHTTPリクエスト
POST /api/v1/messages
Authorization: Bearer [expired_session_token]
Response: 401 Unauthorized
Body: {"error": "INVALID_AUTH_TOKEN", "message": "Session has expired"}
コンソール出力
[OpenClaw] Connection lost, attempting reconnect...
[OpenClaw] WebSocket reconnected
[OpenClaw] Authentication failed: 401
[OpenClaw] Token validation error: Token not found in session store
識別に役立つ特徴
| 条件 | 動作 |
|---|---|
| タブの非アクティブ時間 < 10分 | 正常に動作 |
| タブの非アクティブ時間 > 10〜15分 | メッセージ送信時に401エラー |
| F5 / ページ更新 | 即座に解決 |
| ブラウザコンソールのクリア | 同じ401エラーが継続 |
🧠 原因
アーキテクチャ概要
OpenClaw Control UIは、デュアルトランスポートアーキテクチャを使用しています:
┌─────────────────────────────────────────────────────────────┐
│ Browser Client │
│ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │ localStorage │ │ WebSocket Conn │ │ HTTP Client │ │
│ │ (Auth Token) │───▶│ (Message Bus) │◀───│ (REST API) │ │
│ └──────────────┘ └────────────────┘ └─────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
┌───────▼───────┐
│ OpenClaw Core │
│ (Gateway) │
└────────────────┘
障害発生のシーケンス
- 初期認証: ユーザーが認証を行う。JWTは
openclaw_auth_tokenとしてlocalStorageに保存される - セッション作成: ゲートウェイがサーバー側で、WebSocket接続IDにマッピングされたセッションを作成
- アクティブ期間: 確立されたWebSocketチャネル経由でメッセージが正常に流れる
- タイムアウトトリガー: 約10〜15分後、非アクティブタイムアウトによりサーバー側セッションが期限切れになる
- WebSocket終了: サーバーが
1006コードでWebSocketを閉じるか、「Session expired」フレームを送信する - サイレント再接続: クライアントは再接続を試みるが、再接続ハンドシェイク時にauthトークンを含めない
- 認証失敗: 新しいWebSocket接続は、セッションストアが空のため
401で拒否される
コードパス分析
バグはクライアントの再接続ロジックに存在します:
// Hypothetical problematic code in web-ui/src/services/connection.ts
class ConnectionManager {
async reconnect() {
// ❌ BUG: Does not retrieve token from localStorage
const ws = new WebSocket(this.gatewayUrl);
ws.onopen = () => {
// Missing: this.authenticate();
};
}
// Proper implementation would include:
async authenticate() {
const token = localStorage.getItem('openclaw_auth_token');
this.ws.send(JSON.stringify({
type: 'AUTH',
token: token // ← This step is missing in reconnect
}));
}
}
セッション管理的矛盾
問題はバージョンの不一致によって複雑化しています:
CLI Version: 2026.2.23
Browser Version: 2026.2.17
これは、ゲートウェイが組み込みのWeb UIアセットとは別々に更新された可能性があり、サーバーとクライアントの間でセッション検証ロジックが分岐している可能性があります。
環境固有の要因
| 要因 | 影響 |
|---|---|
GATEWAY_SESSION_TIMEOUT | デフォルト600秒(10分) |
GATEWAY_WEBSOCKET_PING_INTERVAL | 設定されていない可能性があり、TCP keepaliveのギャップが発生する |
| ブラウザタブのバックグラウンド化 | ブラウザはバックグラウンドタブ内のWebSocketを調整する可能性がある |
| macOS電源管理 | ディスプレイスリープ後にタブアクティビティを中断する可能性がある |
🛠️ 解決手順
オプション1: クライアント側の修正(即時 - ユーザー向け)
前提条件: ブラウザ DevToolsへのアクセス(F12)
- ブラウザ DevToolsを開く(F12)
- Consoleタブに移動する
- 非アクティブ期間の前に次のスニペットを実行する:
// Prevent automatic reconnection from dropping auth
(function() {
const originalConnect = window.OpenClawConnection?.connect;
if (originalConnect) {
window.OpenClawConnection.connect = function() {
const token = localStorage.getItem('openclaw_auth_token');
const result = originalConnect.call(this);
// Inject auth after reconnection
this.ws?.addEventListener('open', () => {
this.ws.send(JSON.stringify({
type: 'AUTH',
token: token
}));
});
return result;
};
}
console.log('[OpenClaw] Auth preservation patch applied');
})();
オプション2: サーバー設定(管理者向け)
ステップ1: ゲートウェイ設定ファイルの位置を確認:
# Linux/macOS
~/.openclaw/gateway.yaml
# Docker
docker exec openclaw-gateway cat /app/config/gateway.yaml
ステップ2: セッションタイムアウト設定を変更:
# Before (gateway.yaml)
server:
session_timeout: 600 # 10 minutes
# After (gateway.yaml)
server:
session_timeout: 28800 # 8 hours
websocket:
ping_interval: 30 # Send ping every 30 seconds
ping_timeout: 10 # Disconnect if no pong within 10 seconds
auth:
token_refresh_interval: 300 # Auto-refresh token every 5 minutes
ステップ3: ゲートウェイサービスを再起動:
# Systemd
sudo systemctl restart openclaw-gateway
# Docker
docker restart openclaw-gateway
# Direct binary
./openclaw gateway restart
オプション3: Web UIコード修正(開発者向け)
ファイル: web-ui/src/services/WebSocketManager.ts
// Before (broken reconnect logic)
class WebSocketManager {
private handleReconnect() {
this.socket = new WebSocket(this.url);
// Missing: Authentication on new connection
}
}
// After (corrected implementation)
class WebSocketManager {
private handleReconnect() {
this.socket = new WebSocket(this.url);
this.socket.addEventListener('open', () => {
this.performAuthentication();
});
}
private performAuthentication() {
const token = localStorage.getItem('openclaw_auth_token');
if (token) {
this.send({
type: 'AUTH_HANDSHAKE',
payload: {
token: token,
clientVersion: window.OPENCLAW_VERSION,
reconnect: true
}
});
}
}
}
セッションストアの追加修正:
ファイル: gateway/src/session/SessionStore.ts
// Before: Sessions expire on timeout alone
async createSession(token: string): Promise {
return this.sessions.create({
token,
expiresAt: Date.now() + SESSION_TIMEOUT
});
}
// After: Sessions can be extended via keepalive
async createSession(token: string): Promise {
return this.sessions.create({
token,
expiresAt: Date.now() + SESSION_TIMEOUT,
extendable: true
});
}
async extendSession(sessionId: string): Promise {
const session = await this.sessions.get(sessionId);
if (session?.extendable) {
session.expiresAt = Date.now() + SESSION_TIMEOUT;
await this.sessions.update(sessionId, session);
}
}
オプション4: バージョン同期(バージョンの不一致バグ用)
# Stop all OpenClaw services
openclaw stop --all
# Clear cached assets
rm -rf ~/.openclaw/cache/web-ui/
rm -rf ~/.openclaw/cache/assets/
# Reinstall to sync versions
openclaw update --force
# Restart services
openclaw start --all
🧪 検証
テストケース1: 基本的な再接続
# Terminal 1: Monitor gateway logs
openclaw logs --follow gateway
# Browser: Open DevTools Console and filter for "OpenClaw"
# Execute: Leave tab for exactly 12 minutes
# Expected: No 401 errors after reconnect
# Console should show:
# [OpenClaw] Connection lost
# [OpenClaw] Reconnecting...
# [OpenClaw] Auth sent
# [OpenClaw] Reconnected successfully
テストケース2: WebSocket Ping/Pong検証
# In browser console, verify ping/pong traffic
setInterval(() => {
console.table({
wsState: WebSocket.CONNECTING, // 0
wsOpen: WebSocket.OPEN, // 1
wsClosing: WebSocket.CLOSING, // 2
wsClosed: WebSocket.CLOSED // 3
});
}, 60000); // Check every minute
# Expected: State remains OPEN during backgrounding
テストケース3: セッション持続性の確認
# Check session TTL via gateway API
curl -s http://127.0.0.1:18789/api/v1/session/status \
-H "Authorization: Bearer $(cat ~/.openclaw/auth_token)" \
| jq .session
# Expected output:
# {
# "sessionId": "sess_abc123",
# "expiresAt": "2026-01-16T00:00:00Z",
# "ttl": 28800,
# "extendable": true
# }
テストケース4: 負荷テスト(自動化)
# Run the session timeout test suite
npm test -- --grep "session-timeout"
# Expected results:
# ✓ reconnect-with-auth: No 401 after 15min inactivity
# ✓ token-refresh: Token auto-renewed before expiry
# ✓ multiple-reconnects: Auth preserved across 5 reconnect cycles
検証チェックリスト
| テスト | コマンド | 期待される結果 |
|---|---|---|
| ゲートウェイへのアクセス可能 | curl http://127.0.0.1:18789/health | {“status”: “ok”} |
| WebSocketアップグレード | websocat ws://127.0.0.1:18789/ws | 接続確立 |
| 認証トークンの有効性 | localStorage.openclaw_auth_tokenの確認 | 空でないJWT文字列 |
| バージョンの同期 | openclaw version | ブラウザUIバージョンと一致 |
⚠️ よくある落とし穴
落とし穴1: ブラウザのプライバシー/シークレットモード
問題: localStorageは、厳格モードのIncognito/プライベートブラウジング使用时クリアされます。
症状: ページを更新しても認証に失敗します。
回避策:
# Check if localStorage is accessible
console.log(localStorage.getItem('openclaw_auth_token'));
// If null in Incognito: Use persistent storage option in settings
落とし穴2: Dockerネットワーク分離
問題: ブラウザからのWebSocket接続は、HTTPとは異なるDocker内部ネットワーク経由でルーティングされる場合があります。
症状: 正しいトークんでもHTTP 401エラーが発生する。WebSocketアップグレードが失敗する。
診断:
# Check Docker port mappings
docker port openclaw-gateway
# Verify WebSocket endpoint is exposed
curl -I http://localhost:18789/ws
# Should return: 101 Switching Protocols
修正:
# docker-compose.yaml addition
services:
gateway:
ports:
- "18789:18789" # HTTP/REST
- "18790:18790" # WebSocket (if separate)
落とし穴3: macOSバッテリー/省エネルギーモード
問題: macOSはバックグラウンドタブ内のJavaScript実行を調整し、ping/pong keepaliveを妨げる場合があります。
症状: 短いタイムアウトを設定してもセッションが期限切れになる。
回避策:
# Disable App Nap for browser
# Safari: Develop → Experimental Features → Disable Background Timer Throttling
# Chrome: Add --disable-background-timer-throttling flag
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --disable-background-timer-throttling
落とし穴4: リバースプロキシのタイムアウト
問題: Nginx/Apacheはproxy_read_timeoutのためにWebSocket接続を閉じる場合があります。
症状: 401はゲートウェイタイムアウトではなく、プロキシタイムアウトで正確に発生します。
修正:
# nginx.conf
location /ws {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24 hours
proxy_send_timeout 86400;
}
落とし穴5: マルチタブ認証
問題: 複数のタブを開くと複数のセッションが作成されます。シングルセッションモードを使用している場合、1つを閉じると他のセッションが無効になる可能性があります。
症状: 2番目のタブを開いて閉じると401が発生する。
回避策: ゲートウェイ設定でマルチセッション режимを有効にします:
# gateway.yaml
auth:
allow_concurrent_sessions: true
session_mode: per-tab # Instead of per-user
落とし穴6: トークンの有効期限とセッションの有効期限の混同
問題: JWTはWebSocketセッションとは独立して期限切れになる場合があります。
症状: 認証直後でも401が発生する。
診断:
# Decode JWT to check expiration
atob(localStorage.openclaw_auth_token.split('.')[1])
# Look for "exp" claim - Unix timestamp
# Compare with current time
date +%s
🔗 関連するエラー
関連するHTTPエラー
| エラーコード | 問題 | 関連性 |
|---|---|---|
401 Unauthorized | 無効/期限切れの認証トークン | この問題の直接的な結果 |
403 Forbidden | 有効なトークンだが権限が不十分 | 関連する認証フロー |
407 Proxy Authentication Required | プロキシ資格情報が必要 | 異なるレイヤー |
関連するWebSocket終了コード
| 終了コード | 名前 | 説明 |
|---|---|---|
1000 | Normal Closure | 意図的な切断 |
1001 | Going Away | サーバーのシャットダウン |
1006 | Abnormal Closure | ネットワーク障害またはタイムアウト(今回の場合) |
1011 | Unexpected Error | サーバー側エラー |
4001 | Authentication Failed | カスタムゲートウェイコード |
OpenClawの過去の関連問題
- Issue #2847: "長い会話中にWebSocketがランダムに切断される" - 同様のタイムアウト機構
- Issue #2901: "ブラウザ再起動後に認証トークンが保持されない" - localStorageのエッジケース
- Issue #3102: "CLIとWeb UI間のバージョンが不一致" - このレポートで 注記されたバージョンの相違に関連
- Issue #3156: "ダッシュボードが5分ごとに再ログインを要求する" - より短いタイムアウトの亜種
- Issue #3224: "バックグラウンドタブでWebSocket pingが動作しない" - macOS固有
関連するドキュメント
- OpenClaw Authentication Architecture
- WebSocket Protocol Specification
- Session Management Configuration
- General Troubleshooting Guide
既知の影響を受けるバージョン
Vulnerable versions: 2026.2.10 - 2026.2.23
Fixed in version: 2026.2.24 (pending release)
Partial fix: 2026.2.18 (ping implementation added, but not active by default)