April 15, 2026 • Versión: 2026.3.13

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: unavailable

Comportamiento técnico

  • El comando memory status informa Embeddings: unavailable en lugar de mostrar el proveedor de embeddings remoto configurado.
  • El comando memory search falla 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, o ALL_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 environments

Por qué esto falla en entornos con proxy

En configuraciones de proxy como Clash TUN con modo fake-IP:

  1. El resolutor DNS de la máquina local no puede alcanzar api.ohmygpt.com
  2. La resolución DNS debe ocurrir a través del túnel del proxy (por ejemplo, mediante clashDNS o resolución del lado del proxy)
  3. La llamada a dns.lookup() utiliza el resolutor del sistema, evitando el proxy por completo
  4. 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 main

Opció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 -20

Paso 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 call

Paso 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 sequence

Paso 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 error

Paso 7: Verificar los códigos de salida:

$ openclaw memory search --agent main --query "test"
$ echo $?
0

# Expected: Exit code 0 on success

Salida 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

Problema: La funció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 gateway
  • auth-profiles-DDVivXkv.js — bundle de autenticación alternativo
  • discord-CcCLMjHw.js — ruta del canal de discord

Bundles afectados que pueden tener el parche:

  • auth-profiles-DRjqKE3G.js — ruta de CLI
  • model-selection-*.js — bundles de selección de modelo
  • plugin-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

Problema: 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:7890

Revisar la implementación real de hasProxyEnvConfigured() para confirmar qué variables se verifican.

3. Desajuste de protocolo del proxy

Problema: Las solicitudes HTTPS a través de un proxy HTTP requieren un manejo adecuado del protocolo.

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:1080

4. Caché de bundles después de la corrección en código fuente

Problema: El sistema de compilación puede no recompilar todos los archivos si las compilaciones incrementales están habilitadas.

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

Problema: Al ejecutar OpenClaw en WSL con un proxy del host de Windows, las variables de entorno pueden no propagarse correctamente.

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}'):7890

6. localhost vs 127.0.0.1 en diferentes contextos

Problema: Algunas configuraciones de proxy se vinculan a 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 cuando dns.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 formato http://user:password@host:port en 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 — Contiene fetchWithSsrFGuard(), la función core de fetch con resolución de modo
  • src/ssrf/fetch-mode.ts — Contiene hasProxyEnvConfigured() y withTrustedEnvProxyGuardedFetchMode()
  • src/ssrf/dns-resolve.ts — Contiene resolvePinnedHostnameWithPolicy() que realiza el dns.lookup() bloqueante
  • src/memory/post-json.ts — Contiene withRemoteHttpResponse() que necesita la corrección

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.