Memory Search Remote Embeddings Fallan con ENOTFOUND Cuando el Environment Proxy Está Configurado
La función withRemoteHttpResponse() ignora el modo TRUSTED_ENV_PROXY, causando fallos de pre-resolución DNS local en entornos proxy como el modo fake-IP de Clash TUN o proxies corporativos.
🔍 Síntomas
Manifestación del error
Al ejecutar comandos de búsqueda de memoria con un proxy HTTP configurado, la funcionalidad de embeddings deja de estar disponible:
$ openclaw memory status --deep --agent main
Embeddings: unavailable
Error: getaddrinfo ENOTFOUND api.ohmygpt.com
$ openclaw memory search --agent main --query "test"
Error: getaddrinfo ENOTFOUND api.ohmygpt.com
Embeddings: unavailableComportamiento técnico
- El comando
memory statusinformaEmbeddings: unavailableen lugar de mostrar el proveedor de embeddings remoto configurado. - El comando
memory searchfalla inmediatamente con el error de resolución DNS. - Las llamadas del SDK de OpenAI al mismo endpoint en la misma máquina tienen éxito, lo que confirma que la infraestructura del proxy está funcional.
- El error ocurre antes de que se intente cualquier solicitud HTTP — falla en la etapa de resolución DNS.
Condiciones de reproducción
- Requerido: Proxy HTTP/HTTPS configurado mediante variables de entorno (
HTTPS_PROXY,HTTP_PROXY, oALL_PROXY) - Requerido: Configuración de proxy donde el DNS local no puede resolver el nombre de host del proveedor de embeddings (por ejemplo, modo fake-IP de Clash TUN, DNS corporativo sobre proxy)
- Requerido: Proveedor de embeddings remoto configurado en el agente
Configuración del entorno que activa el error
# Environment variables
HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
# Agent config (openclaw agent config)
models.providers.openai.baseUrl = "https://api.ohmygpt.com/v1"
memorySearch.remote.model = "text-embedding-3-small"🧠 Causa raíz
Análisis de la cadena de llamadas
El fallo ocurre a través de esta secuencia exacta:
withRemoteHttpResponse(params)
→ fetchWithSsrFGuard({ url, init, policy })
→ resolveGuardedFetchMode(params)
→ returns STRICT (no mode field in params)
→ resolvePinnedHostnameWithPolicy(hostname)
→ dns.lookup(hostname)
→ ENOTFOUND (local DNS cannot resolve)Verificación de variables de entorno del proxy faltante
La función withRemoteHttpResponse() está implementada en src/memory/post-json.ts (o punto de entrada equivalente). Llama a fetchWithSsrFGuard() sin establecer el campo mode en los parámetros de la solicitud:
// Current (broken) implementation
async function withRemoteHttpResponse(params) {
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
// MISSING: mode field — defaults to STRICT
});
// ...
}Comportamiento predeterminado en modo STRICT
Cuando mode no está establecido, fetchWithSsrFGuard() llama a resolveGuardedFetchMode(), que devuelve STRICT de forma predeterminada. En modo STRICT, la función siempre ejecuta:
resolvePinnedHostnameWithPolicy(hostname)
→ dns.lookup(hostname) // Blocking DNS resolution via Node.js resolver
→ ENOTFOUND // Fails in proxy environmentsPor qué esto falla en entornos con proxy
En configuraciones de proxy como Clash TUN con modo fake-IP:
- El resolutor DNS de la máquina local no puede alcanzar
api.ohmygpt.com - La resolución DNS debe ocurrir a través del túnel del proxy (por ejemplo, mediante
clashDNSo resolución del lado del proxy) - La llamada a
dns.lookup()utiliza el resolutor del sistema, evitando el proxy por completo - La solicitud nunca llega al proxy porque falla en la resolución del nombre de host
El patrón correcto
El codebase ya contiene la implementación correcta en otras rutas de código. Cuando el proxy de entorno está configurado, se debe usar withTrustedEnvProxyGuardedFetchMode():
// Correct implementation
async function withRemoteHttpResponse(params) {
const useEnvProxy = hasProxyEnvConfigured();
const request = useEnvProxy
? withTrustedEnvProxyGuardedFetchMode({
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
})
: {
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
};
const { response, release } = await fetchWithSsrFGuard(request);
// ...
}En modo TRUSTED_ENV_PROXY, fetchWithSsrFGuard():
- Omite la pre-resolución DNS local mediante
dns.lookup() - Utiliza
EnvHttpProxyAgent()directamente para conexiones HTTP - Delega la resolución DNS a la infraestructura del proxy
🛠️ Solución paso a paso
Opción 1: Corrección en código fuente (Recomendado)
Archivo a modificar: src/memory/post-json.ts (o ubicación equivalente del código fuente)
Antes:
import { fetchWithSsrFGuard } from '../ssrf/fetch-guard';
// ... other imports
async function withRemoteHttpResponse(params: RemoteHttpParams) {
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
// mode not set → defaults to STRICT
});
if (!response.ok) {
const body = await response.text();
release();
throw new RemoteHttpError(params.url, response.status, body);
}
return { response, release };
}Después:
import { fetchWithSsrFGuard } from '../ssrf/fetch-guard';
import { hasProxyEnvConfigured, withTrustedEnvProxyGuardedFetchMode } from '../ssrf/fetch-mode';
// ... other imports
async function withRemoteHttpResponse(params: RemoteHttpParams) {
const useEnvProxy = hasProxyEnvConfigured();
const request = useEnvProxy
? withTrustedEnvProxyGuardedFetchMode({
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
})
: {
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
};
const { response, release } = await fetchWithSsrFGuard(request);
if (!response.ok) {
const body = await response.text();
release();
throw new RemoteHttpError(params.url, response.status, body);
}
return { response, release };
}Opción 2: Solución alternativa en entorno de ejecución
Si no puede modificar el código fuente, establezca la variable NODE_TLS_REJECT_UNAUTHORIZED para omitir los problemas de validación de certificados que pueden agravar el problema de DNS:
# Set both proxy and TLS bypass (use only in controlled environments)
export HTTPS_PROXY=http://127.0.0.1:7890
export HTTP_PROXY=http://127.0.0.1:7890
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Then run OpenClaw
openclaw memory status --deep --agent mainOpción 3: Parche de bundle (Solución temporal)
Si la corrección en código fuente aún no está desplegada y necesita una corrección inmediata:
Paso 1: Identificar los bundles afectados:
grep -l "withRemoteHttpResponse" dist/*.js 2>/dev/null | head -20Paso 2: Crear un script de parche (patch-memory-proxy.js):
const fs = require('fs');
const path = require('path');
const bundles = [
'dist/reply-Bm8VrLQh.js',
'dist/auth-profiles-DDVivXkv.js',
'dist/discord-CcCLMjHw.js'
];
const searchPattern = /async function withRemoteHttpResponse\(params\)\{const\{response,release\}=await fetchWithSsrFGuard\(\{url:params\.url,init:params\.init,policy:params\.ssrfPolicy,auditContext:params\.auditContext\?\?"memory-remote"\}\);/g;
const replacePattern = `async function withRemoteHttpResponse(params){const _useEnvProxy=hasProxyEnvConfigured();const _request=_useEnvProxy?withTrustedEnvProxyGuardedFetchMode({url:params.url,init:params.init,policy:params.ssrfPolicy,auditContext:params.auditContext??"memory-remote"}):{url:params.url,init:params.init,policy:params.ssrfPolicy,auditContext:params.auditContext??"memory-remote"};const{response,release}=await fetchWithSsrFGuard(_request);`;
bundles.forEach(bundle => {
if (fs.existsSync(bundle)) {
let content = fs.readFileSync(bundle, 'utf8');
if (searchPattern.test(content)) {
content = content.replace(searchPattern, replacePattern);
fs.writeFileSync(bundle, content);
console.log(`Patched: ${bundle}`);
}
}
});Paso 3: Ejecutar el parche:
node patch-memory-proxy.js🧪 Verificación
Pasos de verificación
Paso 1: Confirmar que las variables de entorno del proxy están establecidas:
$ echo $HTTPS_PROXY
http://127.0.0.1:7890
$ echo $HTTP_PROXY
http://127.0.0.1:7890
$ echo $ALL_PROXY
# (should be empty or set)Paso 2: Verificar que el DNS local no puede resolver el endpoint (confirma la condición):
$ node -e "require('dns').lookup('api.ohmygpt.com', (err, addr) => console.log(err ? err.message : addr))"
getaddrinfo ENOTFOUND api.ohmygpt.com
# Expected: ENOTFOUND error (confirms proxy DNS requirement)Paso 3: Verificar que el parche se ha aplicado revisando el código en el bundle:
$ grep -o "withTrustedEnvProxyGuardedFetchMode" dist/*.js | head -5
dist/reply-Bm8VrLQh.js:withTrustedEnvProxyGuardedFetchMode
dist/auth-profiles-DDVivXkv.js:withTrustedEnvProxyGuardedFetchMode
dist/discord-CcCLMjHw.js:withTrustedEnvProxyGuardedFetchMode
# All affected bundles should contain the function callPaso 4: Verificar que la verificación de hasProxyEnvConfigured está presente:
$ grep -A1 "hasProxyEnvConfigured()" dist/*.js | grep -B1 "withTrustedEnvProxyGuardedFetchMode" | head -10
const _useEnvProxy=hasProxyEnvConfigured();const _request=_useEnvProxy?withTrustedEnvProxyGuardedFetchMode
# Should see both functions in sequencePaso 5: Ejecutar el comando de estado de memoria:
$ openclaw memory status --deep --agent main
Memory Status
├─ Vector Store: OK (50 vectors indexed)
├─ Embeddings: api.ohmygpt.com/text-embedding-3-small
└─ Status: ready
# Expected: Embeddings should show the configured provider, not "unavailable"Paso 6: Ejecutar el comando de búsqueda de memoria:
$ openclaw memory search --agent main --query "test" --limit 5
[
{
"id": "mem_001",
"score": 0.9234,
"content": "..."
}
]
# Expected: Returns search results without ENOTFOUND errorPaso 7: Verificar los códigos de salida:
$ openclaw memory search --agent main --query "test"
$ echo $?
0
# Expected: Exit code 0 on successSalida esperada después de la corrección
$ openclaw memory status --deep --agent main
Memory Status
├─ Vector Store
│ └─ Provider: remote
│ └─ Model: text-embedding-3-small
│ └─ Dimensions: 1536
│ └─ Vectors: 50
├─ Embeddings
│ └─ Status: available
│ └─ Endpoint: https://api.ohmygpt.com/v1
│ └─ Model: text-embedding-3-small
└─ Search: functional⚠️ Errores comunes
1. Duplicación de artefactos de compilación
withRemoteHttpResponse() es insertada en línea por rolldown en múltiples fragmentos de dist. En la versión 2026.3.13, hay 7 copias del bundle en diferentes archivos.Bundles afectados que pueden no tener el parche:
reply-Bm8VrLQh.js— ruta de herramientas del agente gatewayauth-profiles-DDVivXkv.js— bundle de autenticación alternativodiscord-CcCLMjHw.js— ruta del canal de discord
Bundles afectados que pueden tener el parche:
auth-profiles-DRjqKE3G.js— ruta de CLImodel-selection-*.js— bundles de selección de modeloplugin-sdk/thread-bindings-*.js— bundles del SDK de plugins
Mitigación: Siempre recompilar desde código fuente y verificar que todas las copias del bundle contengan la guarda del proxy después de compilar.
2. Sensibilidad a mayúsculas en variables de entorno
hasProxyEnvConfigured() puede verificar nombres de variables específicos con mayúsculas específicas.Asegurar mayúsculas consistentes para las variables del proxy:
# Correct (uppercase)
export HTTPS_PROXY=http://127.0.0.1:7890
# May not be detected
export https_proxy=http://127.0.0.1:7890Revisar la implementación real de hasProxyEnvConfigured() para confirmar qué variables se verifican.
3. Desajuste de protocolo del proxy
Asegurar que la URL del proxy incluya el protocolo correcto:
# Correct for HTTP proxy
export HTTPS_PROXY=http://127.0.0.1:7890
# Correct for SOCKS5 proxy
export HTTPS_PROXY=socks5://127.0.0.1:10804. Caché de bundles después de la corrección en código fuente
Forzar recompilación limpia:
# Remove dist directory
rm -rf dist/
# Clear any cache
rm -rf node_modules/.cache
# Rebuild
npm run build
# Verify all copies
grep -l "withRemoteHttpResponse" dist/**/*.js | xargs grep -l "hasProxyEnvConfigured"5. Proxy entre WSL/Windows en entornos cruzados
Establecer el proxy explícitamente en WSL:
# In WSL, get Windows host IP
export HTTPS_PROXY=http://$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):78906. localhost vs 127.0.0.1 en diferentes contextos
127.0.0.1 mientras otras usan ::1 (localhost IPv6).Asegurar que el proxy está escuchando en la interfaz correcta:
# Check proxy binding
netstat -tlnp | grep 7890
# Common result: tcp 0 0 127.0.0.1:7890 0.0.0.0:* LISTEN🔗 Errores relacionados
Códigos de error e issues relacionadas
ENOTFOUND— Fallo de resolución DNS. Ocurre cuandodns.lookup()no puede resolver el nombre de host a través del resolutor local. En entornos con proxy, esto es esperado cuando el endpoint debe ser resuelto a través del túnel del proxy.ECONNREFUSED— Conexión rechazada. Puede ocurrir si el proxy no está en ejecución o la dirección del proxy en las variables de entorno es incorrecta.ETIMEDOUT— Tiempo de conexión agotado. Puede ocurrir si el proxy es inalcanzable o el enrutamiento de red está mal configurado.Proxy Authentication Required— Proxies corporativos que requieren credenciales. Asegurar que se usa el formatohttp://user:password@host:porten las URLs del proxy.
Issues históricamente relacionadas
- SSRF Guard Bypass in Memory Module — Issue #4521 — Corrección relacionada para
withTrustedEnvProxyGuardedFetchMode()que no se usa en la ruta de embeddings. - DNS Resolution Fails Behind Corporate Proxy — Issue #3204 — Problemas generales de resolución DNS + proxy en entornos empresariales.
- Memory Search Returns Empty Results — Issue #4892 — El síntoma puede manifestarse como resultados vacíos cuando los embeddings no están disponibles debido a problemas de DNS del proxy.
- Embeddings Provider Timeout in Docker — Issue #5103 — Interacción de red Docker + proxy causando tiempos de espera en operaciones de memoria.
- SSRF Policy Not Respected in Remote Embeddings — Issue #5017 — Concern de seguridad sobre las guards SSRF que no se aplican a las llamadas de fetch de embeddings de memoria.
Dependencias clave en la cadena de corrección
src/ssrf/fetch-guard.ts— ContienefetchWithSsrFGuard(), la función core de fetch con resolución de modosrc/ssrf/fetch-mode.ts— ContienehasProxyEnvConfigured()ywithTrustedEnvProxyGuardedFetchMode()src/ssrf/dns-resolve.ts— ContieneresolvePinnedHostnameWithPolicy()que realiza eldns.lookup()bloqueantesrc/memory/post-json.ts— ContienewithRemoteHttpResponse()que necesita la corrección