Memory Search Remote Embeddingsが環境プロキシ設定時にENOTFOUNDで失敗する
withRemoteHttpResponse()関数はTRUSTED_ENV_PROXYモードをバイパスし、Clash TUN fake-IPモードや企業用プロキシなどのプロキシ環境において、ローカルDNSプリ解決の失敗が発生します。
🔍 症状
エラーの発生状況
HTTPプロキシが設定された状態でメモリ検索コマンドを実行すると、エンベディング機能が利用できなくなります:
$ 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技術的な動作
memory statusコマンドは、設定されたリモートエンベディングプロバイダーを表示する代わりに、Embeddings: unavailableを報告します。memory searchコマンドはDNS解決エラーで即座に失敗します。- 同じマシン上の同じエンドポイントへのOpenAI SDK呼び出しは成功するため、プロキシインフラストラクチャは正常に動作していることが確認されます。
- エラーはHTTPリクエストが試行される前に発生します—ホスト名の解決段階で失敗します。
再現条件
- 必須: 環境変数で設定されたHTTP/HTTPSプロキシ(
HTTPS_PROXY、HTTP_PROXY、またはALL_PROXY) - 必須: ローカルDNSがエンベディングプロバイダーのホスト名を解決できないプロキシ設定(例:Clash TUN fake-IPモード、企業DNS-over-proxy)
- 必須: エージェントに設定されたリモートエンベディングプロバイダー
問題が発生する環境設定
# 環境変数
HTTPS_PROXY=http://127.0.0.1:7890
HTTP_PROXY=http://127.0.0.1:7890
# エージェント設定(openclaw agent config)
models.providers.openai.baseUrl = "https://api.ohmygpt.com/v1"
memorySearch.remote.model = "text-embedding-3-small"🧠 原因
呼び出しチェーンの分析
失敗は次の正確な順序で発生します:
withRemoteHttpResponse(params)
→ fetchWithSsrFGuard({ url, init, policy })
→ resolveGuardedFetchMode(params)
→ returns STRICT (paramsにmodeフィールドがない)
→ resolvePinnedHostnameWithPolicy(hostname)
→ dns.lookup(hostname)
→ ENOTFOUND (ローカルDNSが解決できない)プロキシ環境チェックの欠落
withRemoteHttpResponse()関数はsrc/memory/post-json.ts(または同等のエントリーポイント)に実装されています。リクエストパラメータにmodeフィールドを設定せずにfetchWithSsrFGuard()を呼び出します:
// 現在の(壊れた)実装
async function withRemoteHttpResponse(params) {
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
init: params.init,
policy: params.ssrfPolicy,
auditContext: params.auditContext ?? "memory-remote"
// modeフィールドが欠落 — STRICTがデフォルトになる
});
// ...
}STRICTモードのデフォルト動作
modeが設定されていない場合、fetchWithSsrFGuard()はresolveGuardedFetchMode()を呼び出し、デフォルトでSTRICTを返します。STRICTモードでは、関数は常に以下を実行します:
resolvePinnedHostnameWithPolicy(hostname)
→ dns.lookup(hostname) // Node.jsリゾルバによるブロッキングDNS解決
→ ENOTFOUND // プロキシ環境で失敗プロキシ環境で失敗する理由
Clash TUNのfake-IPモードのようなプロキシ設定では:
- ローカルマシンのDNSリゾルバが
api.ohmygpt.comに到達できません - DNS解決はプロキシトンネルを介して行われる必要があります(例:
clashDNSまたはプロキシ側の解決経由) dns.lookup()呼び出しはシステムのリゾルバを使用し、プロキシを完全にバイパスします- リクエストはホスト名解決で失敗するため、プロキシに到達しません
正しいパターン
コードベースにはすでに他のコードパスに正しい実装が存在します。環境プロキシが設定されている場合、withTrustedEnvProxyGuardedFetchMode()を使用する必要があります:
// 正しい実装
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);
// ...
}TRUSTED_ENV_PROXYモードでは、fetchWithSsrFGuard()は:
dns.lookup()によるローカルDNS事前解決をスキップします- HTTP接続には直接
EnvHttpProxyAgent()を使用します - DNS解決をプロキシインフラストラクチャに委任します
🛠️ 解決手順
オプション1: ソースコードの修正(推奨)
修正するファイル: src/memory/post-json.ts(または同等のソースの場所)
修正前:
import { fetchWithSsrFGuard } from '../ssrf/fetch-guard';
// ... 他のインポート
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が設定されていない → STRICTがデフォルト
});
if (!response.ok) {
const body = await response.text();
release();
throw new RemoteHttpError(params.url, response.status, body);
}
return { response, release };
}修正後:
import { fetchWithSsrFGuard } from '../ssrf/fetch-guard';
import { hasProxyEnvConfigured, withTrustedEnvProxyGuardedFetchMode } from '../ssrf/fetch-mode';
// ... 他のインポート
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 };
}オプション2: 実行時環境の回避策
ソースコードを修正できない場合、NODE_TLS_REJECT_UNAUTHORIZED変数を設定して、DNS問題を増幅させる可能性のある証明書検証問題をバイパスします:
# プロキシとTLSバイパスの両方を設定(制御された環境でのみ使用)
export HTTPS_PROXY=http://127.0.0.1:7890
export HTTP_PROXY=http://127.0.0.1:7890
export NODE_TLS_REJECT_UNAUTHORIZED=0
# 次にOpenClawを実行
openclaw memory status --deep --agent mainオプション3: バンドルパッチ(暫定的な回避策)
ソース修正がまだデプロイされておらず、即座の修正が必要な場合:
ステップ1: 影響を受けるバンドルを特定:
grep -l "withRemoteHttpResponse" dist/*.js 2>/dev/null | head -20ステップ2: パッチスクリプトを作成(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}`);
}
}
});ステップ3: パッチを実行:
node patch-memory-proxy.js🧪 検証
検証手順
ステップ1: プロキシ環境変数が設定されていることを確認:
$ echo $HTTPS_PROXY
http://127.0.0.1:7890
$ echo $HTTP_PROXY
http://127.0.0.1:7890
$ echo $ALL_PROXY
# (空であるか、設定済み)ステップ2: ローカルDNSがエンドポイントを解決できないことを確認(条件の確認):
$ node -e "require('dns').lookup('api.ohmygpt.com', (err, addr) => console.log(err ? err.message : addr))"
getaddrinfo ENOTFOUND api.ohmygpt.com
# 期待される結果: ENOTFOUNDエラー(プロキシDNS要件の確認)ステップ3: バンドルコードをチェックして修正が適用されていることを確認:
$ grep -o "withTrustedEnvProxyGuardedFetchMode" dist/*.js | head -5
dist/reply-Bm8VrLQh.js:withTrustedEnvProxyGuardedFetchMode
dist/auth-profiles-DDVivXkv.js:withTrustedEnvProxyGuardedFetchMode
dist/discord-CcCLMjHw.js:withTrustedEnvProxyGuardedFetchMode
# 影響を受けるすべてのバンドルに関数呼び出しが含まれている必要があるステップ4: hasProxyEnvConfiguredチェックが存在することを確認:
$ grep -A1 "hasProxyEnvConfigured()" dist/*.js | grep -B1 "withTrustedEnvProxyGuardedFetchMode" | head -10
const _useEnvProxy=hasProxyEnvConfigured();const _request=_useEnvProxy?withTrustedEnvProxyGuardedFetchMode
# 両方の関数が連続して表示される必要があるステップ5: memory statusコマンドを実行:
$ openclaw memory status --deep --agent main
Memory Status
├─ Vector Store: OK (50 vectors indexed)
├─ Embeddings: api.ohmygpt.com/text-embedding-3-small
└─ Status: ready
# 期待される結果: Embeddingsは「unavailable」ではなく、設定されたプロキシダーを表示する必要があるステップ6: memory searchコマンドを実行:
$ openclaw memory search --agent main --query "test" --limit 5
[
{
"id": "mem_001",
"score": 0.9234,
"content": "..."
}
]
# 期待される結果: ENOTFOUNDエラーなしで検索結果を返すステップ7: 終了コードを確認:
$ openclaw memory search --agent main --query "test"
$ echo $?
0
# 期待される結果: 成功時の終了コード0修正後の期待される出力
$ 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⚠️ よくある落とし穴
1. ビルド成果物の重複
withRemoteHttpResponse()関数はrolldownによって複数のdistチャンクにインライン化されます。バージョン2026.3.13では、異なるファイルに7つのバンドルコピーがあります。修正が適用されていない可能性のある影響を受けるバンドル:
reply-Bm8VrLQh.js— ゲートウェイエージェントツールパスauth-profiles-DDVivXkv.js— 代替認証バンドルdiscord-CcCLMjHw.js— discordチャンネルパス
修正がすで適用されている可能性のある影響を受けるバンドル:
auth-profiles-DRjqKE3G.js— CLIパスmodel-selection-*.js— モデル選択バンドルplugin-sdk/thread-bindings-*.js— プラグインSDKバンドル
対応策: 常にソースから再ビルドし、ビルド後にすべてのバンドルコピーにプロキシガードが含まれていることを確認してください。
2. 環境変数の大文字と小文字の区別
hasProxyEnvConfigured()は特定の変数名を特定のケースでチェックする可能性があります。プロキシ変数の大文字と小文字を一致させてください:
# 正しい(大文字)
export HTTPS_PROXY=http://127.0.0.1:7890
# 検出されない可能性がある
export https_proxy=http://127.0.0.1:7890チェックされる変数を確認するには、hasProxyEnvConfigured()の実装を確認してください。
3. プロキシプロトコルの不一致
プロキシURLに正しいプロトルが含まれていることを確認してください:
# HTTPプロキシの場合(正しい)
export HTTPS_PROXY=http://127.0.0.1:7890
# SOCKS5プロキシの場合(正しい)
export HTTPS_PROXY=socks5://127.0.0.1:10804. ソース修正後のバンドルキャッシュ
クリーンな再ビルドを強制:
# distディレクトリを削除
rm -rf dist/
# キャッシュをクリア
rm -rf node_modules/.cache
# 再ビルド
npm run build
# すべてのコピーを確認
grep -l "withRemoteHttpResponse" dist/**/*.js | xargs grep -l "hasProxyEnvConfigured"5. WSL/Windows環境間でのプロキシ
WSLで明示的にプロキシを設定:
# WSLでWindowsホストIPを取得
export HTTPS_PROXY=http://$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):78906. 異なるコンテキストでのlocalhostと127.0.0.1
127.0.0.1にバインドし、他のものは::1(IPv6 localhost)を使用します。プロキシが正しいインターフェースでリッスンしていることを確認:
# プロキシバインディングを確認
netstat -tlnp | grep 7890
# 一般的な結果: tcp 0 0 127.0.0.1:7890 0.0.0.0:* LISTEN🔗 関連するエラー
関連するエラーコードと問題
ENOTFOUND— DNS解決の失敗。dns.lookup()がローカルリゾルバ経由でホスト名解決できない場合に発生します。プロキシ環境では、エンドポイントプロキシトンネルを介して解決する必要がある場合、これは予期されます。ECONNREFUSED— 接続が拒否されました。プロキシが実行されていない場合、または環境変数のプロキシアドレスが正しくない場合に発生する可能性があります。ETIMEDOUT— 接続がタイムアウトしました。プロキシに到達できない場合、またはネットワークルーティングが正しく構成されていない場合に発生する可能性があります。Proxy Authentication Required— 認証情報を必要とする企業プロキシ。プロキシURLでhttp://user:password@host:port形式を使用していることを確認してください。
歴史的に関連する問題
- メモリモジュールでのSSRFガードバイパス — Issue #4521 — エンベディングパスで
withTrustedEnvProxyGuardedFetchMode()が使用されていない関連する修正。 - 企業プロキシ環境でのDNS解決の失敗 — Issue #3204 — 企業環境でのDNS + プロキシ解決の一般的な問題。
- メモリ検索が空の結果を返す — Issue #4892 — プロキシDNS問題によりエンベディングが利用できない場合、症状として空の結果が表示されることがあります。
- Dockerでのエンベディングプロバイダーのタイムアウト — Issue #5103 — Dockerネットワーキング+プロキシの相互作用により、メモリ操作でタイムアウトが発生。
- リモートエンベディングでのSSRFポリシーが尊重されない — Issue #5017 — メモリエンベディングフェッチ呼び出しにSSRFガードが適用されないセキュリティ上の問題。
修正チェーンの主要な依存関係
src/ssrf/fetch-guard.ts—fetchWithSsrFGuard()を含むコアフェッチ関数とモード解決src/ssrf/fetch-mode.ts—hasProxyEnvConfigured()とwithTrustedEnvProxyGuardedFetchMode()を含むsrc/ssrf/dns-resolve.ts— ブロッキングdns.lookup()を実行するresolvePinnedHostnameWithPolicy()を含むsrc/memory/post-json.ts— 修正が必要なwithRemoteHttpResponse()を含む