Error HTTP 401 tras inactividad en la interfaz de control - Web UI Session Timeout: HTTP 401 After Inactivity in OpenClaw Control UI
La interfaz de control de OpenClaw devuelve HTTP 401 Autenticación inválida después de 10-15 minutos de inactividad debido a la expiración de la sesión WebSocket sin re-autenticación automática del token al reconectar.
🔍 Síntomas
Manifestación Principal
Después de aproximadamente 10-15 minutos de inactividad de la pestaña del navegador, al intentar enviar un mensaje a través de la interfaz de usuario de OpenClaw Control, se produce un fallo de autenticación:
HTTP 401: Invalid Authentication
Status: 401 Unauthorized
X-Error-Code: INVALID_AUTH_TOKEN
Secuencia de Reproducción
- Navega a
http://127.0.0.1:18789/en el navegador - Autentícate con el token de gateway (se almacena automáticamente en
localStorage) - Envía 2-3 mensajes correctamente a través de la conexión WebSocket
- Deja la pestaña inactiva durante 10-15 minutos (no la cierres)
- Vuelve a la pestaña e intenta enviar un nuevo mensaje
- Observa el error
401en la interfaz de usuario y la consola de red
Actividad de Red Durante el Fallo
Al examinar las DevTools del navegador (F12 → pestaña Red) se revela:
# WebSocket connection state
Connection State: CONNECTING → CLOSED
Close Code: 1006 (Abnormal Closure)
Close Reason: "Session expired"
# Subsequent HTTP requests
POST /api/v1/messages
Authorization: Bearer [expired_session_token]
Response: 401 Unauthorized
Body: {"error": "INVALID_AUTH_TOKEN", "message": "Session has expired"}
Salida de la Consola
[OpenClaw] Connection lost, attempting reconnect...
[OpenClaw] WebSocket reconnected
[OpenClaw] Authentication failed: 401
[OpenClaw] Token validation error: Token not found in session store
Características Distintivas
| Condición | Comportamiento |
|---|---|
| Pestaña inactiva < 10 min | Operación normal |
| Pestaña inactiva > 10-15 min | Errores 401 al enviar mensajes |
| F5 / Actualizar página | Resolución inmediata |
| Limpiar consola del navegador | El mismo error 401 persiste |
🧠 Causa Raíz
Descripción General de la Arquitectura
La interfaz de usuario de control de OpenClaw utiliza una arquitectura de transporte dual:
┌─────────────────────────────────────────────────────────────┐
│ Browser Client │
│ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │ localStorage │ │ WebSocket Conn │ │ HTTP Client │ │
│ │ (Auth Token) │───▶│ (Message Bus) │◀───│ (REST API) │ │
│ └──────────────┘ └────────────────┘ └─────────────┘ │
└────────────────────────────┬────────────────────────────────┘
│
┌───────▼───────┐
│ OpenClaw Core │
│ (Gateway) │
└────────────────┘
Secuencia del Fallo
- Autenticación Inicial: El usuario se autentica; el JWT se almacena en
localStoragecomoopenclaw_auth_token - Creación de Sesión: El gateway crea una sesión del lado del servidor mapeada al ID de conexión WebSocket
- Período Activo: Los mensajes fluyen correctamente a través del canal WebSocket establecido
- Activación del Tiempo de Espera: Después de ~10-15 minutos, la sesión del lado del servidor expira debido al tiempo de inactividad
- Cierre de WebSocket: El servidor cierra WebSocket con el código
1006o envía un marco deSession expired - Reconexión Silenciosa: El cliente intenta reconectar pero NO incluye el token de autenticación en el protocolo de reconexión
- Fallo de Autenticación: La nueva conexión WebSocket es rechazada con
401porque el almacén de sesiones está vacío
Análisis de la Ruta del Código
El error reside en la lógica de reconexión del cliente:
// 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
}));
}
}
Discrepancia en la Gestión de Sesiones
El problema se ve exacerbado por una diferencia de versiones:
CLI Version: 2026.2.23
Browser Version: 2026.2.17
Esto indica que el gateway puede haber sido actualizado por separado de los recursos web integrados en la interfaz de usuario, lo que podría causar que la lógica de validación de sesiones diverja entre el servidor y el cliente.
Factores Específicos del Entorno
| Factor | Impacto |
|---|---|
GATEWAY_SESSION_TIMEOUT | Valor predeterminado 600 segundos (10 min) |
GATEWAY_WEBSOCKET_PING_INTERVAL | Puede no estar configurado, causando gaps en el keepalive de TCP |
| Pestaña en segundo plano del navegador | Los navegadores pueden regular WebSocket en pestañas en segundo plano |
| Gestión de energía de macOS | Puede suspender la actividad de la pestaña después de que la pantalla entre en reposo |
🛠️ Solución Paso a Paso
Opción 1: Corrección del Lado del Cliente (Inmediata - Para Usuarios)
Requisito previo: Acceso a DevTools del navegador (F12)
- Abre las DevTools del navegador (F12)
- Navega a la pestaña Consola
- Ejecuta el siguiente fragmento antes del período de inactividad:
// 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');
})();
Opción 2: Configuración del Servidor (Para Administradores)
Paso 1: Localiza el archivo de configuración del gateway:
# Linux/macOS
~/.openclaw/gateway.yaml
# Docker
docker exec openclaw-gateway cat /app/config/gateway.yaml
Paso 2: Modifica la configuración del tiempo de espera de sesión:
# 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
Paso 3: Reinicia el servicio del gateway:
# Systemd
sudo systemctl restart openclaw-gateway
# Docker
docker restart openclaw-gateway
# Direct binary
./openclaw gateway restart
Opción 3: Corrección del Código de la Interfaz Web (Para Desarrolladores)
Archivo: 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
}
});
}
}
}
Corrección Adicional para el Almacén de Sesiones:
Archivo: 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);
}
}
Opción 4: Sincronización de Versiones (Para el Error de Desajuste de Versiones)
# 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
🧪 Verificación
Caso de Prueba 1: Reconexión Básica
# 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
Caso de Prueba 2: Verificación de Ping/Pong de WebSocket
# 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
Caso de Prueba 3: Verificación de Persistencia de Sesión
# 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
# }
Caso de Prueba 4: Prueba de Carga (Automatizada)
# 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
Lista de Verificación
| Prueba | Comando | Resultado Esperado |
|---|---|---|
| Gateway accesible | curl http://127.0.0.1:18789/health | {“status”: “ok”} |
| Actualización WebSocket | websocat ws://127.0.0.1:18789/ws | Conexión establecida |
| Token de autenticación válido | Verificar localStorage.openclaw_auth_token | Cadena JWT no vacía |
| Sincronización de versiones | openclaw version | Coincide con la versión de la interfaz del navegador |
⚠️ Errores Comunes
Error 1: Modo Privado/Incógnito del Navegador
Problema: localStorage se borra al usar navegación privada en modo estricto.
Síntoma: Incluso después de actualizar la página, la autenticación falla.
Solución alternativa:
# Check if localStorage is accessible
console.log(localStorage.getItem('openclaw_auth_token'));
// If null in Incognito: Use persistent storage option in settings
Error 2: Segmentación de Red de Docker
Problema: Las conexiones WebSocket desde el navegador pueden enrutarse a través de la red interna de Docker de manera diferente a HTTP.
Síntoma: HTTP 401 incluso con el token correcto; la actualización de WebSocket falla.
Diagnóstico:
# Check Docker port mappings
docker port openclaw-gateway
# Verify WebSocket endpoint is exposed
curl -I http://localhost:18789/ws
# Should return: 101 Switching Protocols
Corrección:
# docker-compose.yaml addition
services:
gateway:
ports:
- "18789:18789" # HTTP/REST
- "18790:18790" # WebSocket (if separate)
Error 3: Ahorrador de Energía/Batería de macOS
Problema: macOS puede regular la ejecución de JavaScript en pestañas en segundo plano, impidiendo el keepalive de ping/pong.
Síntoma: Las sesiones expiran incluso con un tiempo de espera corto configurado.
Solución alternativa:
# 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
Error 4: Tiempo de Espera del Proxy Inverso
Problema: Nginx/Apache puede cerrar las conexiones WebSocket debido a proxy_read_timeout.
Síntoma: 401 aparece exactamente en el tiempo de espera del proxy, no en el del gateway.
Corrección:
# 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;
}
Error 5: Autenticación de Múltiples Pestañas
Problema: Abrir múltiples pestañas crea múltiples sesiones; cerrar una puede invalidar otras si se usa el modo de sesión única.
Síntoma: 401 aparece después de abrir una segunda pestaña y cerrarla.
Solución alternativa: Habilita el modo multi-sesión en la configuración del gateway:
# gateway.yaml
auth:
allow_concurrent_sessions: true
session_mode: per-tab # Instead of per-user
Error 6: Expiración del Token vs. Expiración de la Sesión
Problema: El JWT puede expirar independientemente de la sesión WebSocket.
Síntoma: 401 incluso inmediatamente después de la autenticación.
Diagnóstico:
# Decode JWT to check expiration
atob(localStorage.openclaw_auth_token.split('.')[1])
# Look for "exp" claim - Unix timestamp
# Compare with current time
date +%s
🔗 Errores Relacionados
Errores HTTP Relacionados
| Código de Error | Problema | Conexión |
|---|---|---|
401 Unauthorized | Token de autenticación inválido/expirado | Descendiente directo de este problema |
403 Forbidden | Token válido pero permisos insuficientes | Flujo de autenticación relacionado |
407 Proxy Authentication Required | Se requieren credenciales del proxy | Diferente capa |
Códigos de Cierre de WebSocket Relacionados
| Código de Cierre | Nombre | Descripción |
|---|---|---|
1000 | Cierre Normal | Desconexión intencional |
1001 | Yéndonos | El servidor se está apagando |
1006 | Cierre Anormal | Fallo de red o tiempo de espera (nuestro caso) |
1011 | Error Inesperado | Error del lado del servidor |
4001 | Autenticación Fallida | Código personalizado del gateway |
Problemas Históricos en OpenClaw
- Problema #2847: "Las conexiones WebSocket se desconectan aleatoriamente durante conversaciones largas" - Mecanismo de tiempo de espera similar
- Problema #2901: "El token de autenticación no persiste al reiniciar el navegador" - Caso límite de localStorage
- Problema #3102: "Desajuste de versión entre CLI e interfaz web" - Relacionado con la discrepancia de versiones noted en este informe
- Problema #3156: "El panel requiere volver a iniciar sesión cada 5 minutos" - Variante de tiempo de espera más corto
- Problema #3224: "El ping de WebSocket no funciona en pestañas en segundo plano" - Específico de macOS
Documentación Relacionada
- Arquitectura de Autenticación de OpenClaw
- Especificación del Protocolo WebSocket
- Configuración de Gestión de Sesiones
- Guía General de Resolución de Problemas
Versiones Conocidamente Afectadas
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)