1. OpenClaw Gateway 在 WSL 里报 ECONNREFUSED 到底卡在哪
OpenClaw Gateway 是一个跑在本机、负责把外部事件(比如飞书回调、Webhook、定时任务)转发给 Agent 运行时的网关服务,它本身监听一个本地端口(常见是 127.0.0.1:18789),同时又要主动往外发 HTTPS 请求去换取 tenant_access_token 之类的凭证。适合谁?适合在 WSL2 里做本地 Agent 开发、又想让服务常驻后台的人。问题就出在“主动往外发请求”这一步:日志里蹦出AxiosError: connect ECONNREFUSED 127.0.0.1:7890,请求目标是https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal。
这句话翻译成人话就是:程序想通过本机 7890 端口的代理去访问外网,但那个端口上根本没有服务在监听,于是连接被直接拒绝。ECONNREFUSED 和超时(ETIMEDOUT)不一样,超时是“敲门没人应”,拒绝是“门牌号存在但没人住”——说明代理地址是写死的、明确的,只是服务没了。
很多人第一反应是“Gateway 挂了”,其实不是。你跑一下openclaw gateway status,大概率看到的是:
Runtime: running (pid 1528, state active) RPC probe: ok Listening: 127.0.0.1:18789服务活着、systemd 托管正常、websocket 探测通过、端口也在听。也就是说 Gateway 进程本身健康,坏的是它继承来的网络代理配置。Node 生态里 Axios 会自动读取HTTP_PROXY、HTTPS_PROXY、http_proxy、https_proxy、ALL_PROXY这几个环境变量,只要有一个存在且指向 127.0.0.1:7890,请求链路就会变成“程序 → 本地代理 → 外网 API”,代理一没,整条链路断在第一步。
WSL 环境特别容易踩这个坑,因为代理变量可能来自三个地方:一是你在.bashrc/.zshrc里手动 export 的;二是 Windows 侧通过 WSL 的镜像网络或WSLENV透传进来的;三是 systemd user service 在启动时把当时 shell 的环境整个继承了过去。前两个只影响交互式终端,第三个才是让 Gateway “重启也没用”的元凶——你 unset 了当前 shell,但 systemd 启动的进程用的是它自己那份快照。所以排查顺序必须是:先确认 Gateway 状态,再查当前 shell,最后一定要查进程自己的/proc/<pid>/environ,否则你会陷入“明明删了代理还是报错”的循环。
2. 把 endpoint 切到 TaoToken 统一通道的前置准备
定位到代理问题之后,还有一个更省心的做法:与其让 Gateway 去直连各种外部 API、还要跟代理变量斗智斗勇,不如把出站 endpoint 统一收敛到一个稳定的通道上。我这边用的是 TaoToken,官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它提供统一的 API 入口,Base URL 是https://taotoken.net/api,模型对话、Coding Plan、控制台、API Keys、接入文档都有对应页面。
为什么这一步能顺带解决 ECONNREFUSED?因为当你把 Gateway 的出站目标从“一堆分散的外部域名”改成“一个固定的、可直连的 endpoint”后,代理变量是否残留就不再是致命问题了——只要这个 endpoint 本身可达,请求就不会再走 127.0.0.1:7890。当然,前提是你得先把残留的代理变量清干净,否则请求还是会先撞到那个不存在的代理上。
前置准备分三件事。第一,拿到 API Key:进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 创建一个,复制出来形如sk-xxxx的字符串,注意只显示一次。第二,确认你要用的 Model ID,这个在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 或者接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里能查到,不同模型 ID 不一样,别照抄别人的。第三,想清楚你是临时验证还是长期跑:临时验证用模型对话页最省事;长期编码或跑 Agent,建议直接上 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,额度模型更适合常驻服务。
这里有个关键点:TaoToken 的三件套永远是 Base URL + Key + Model ID,缺一不可。Base URL 固定https://taotoken.net/api,Key 是你刚建的,Model ID 按文档填。后面所有配置文件里出现的base_url、api_key、model都对应这三样。如果你用的是 Claude Code 这类工具,接入文档里有专门的 Anthropic 兼容说明,页面在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,照着填就行。
3. 可复制的 gateway 配置片段与 systemd 单元示例
先说 Gateway 侧的配置。OpenClaw 的 gateway 配置一般放在~/.config/openclaw/gateway.json或项目目录下的gateway.config.json,具体路径以openclaw gateway status输出里的 config 路径为准。把出站 endpoint 指向 TaoToken 统一通道,配置片段如下:
{ "gateway": { "listen": "127.0.0.1:18789", "outbound": { "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的Key", "model": "你的ModelID", "timeoutMs": 30000 } }, "proxy": { "enabled": false } }注意proxy.enabled显式设为 false,这是防止 Gateway 内部再自己去读环境变量拼代理。有些版本字段名是useProxy或httpProxy,以你本地openclaw gateway config --help为准,但思路一致:把代理开关关掉,让出站走直连。
然后是 systemd user 单元。Gateway 被 systemd 托管时,环境变量是在单元文件里定义的,所以光改 shell 没用。单元文件通常在~/.config/systemd/user/openclaw-gateway.service,示例:
[Unit] Description=OpenClaw Gateway After=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/openclaw gateway start --foreground Restart=on-failure RestartSec=5 Environment=NODE_ENV=production Environment=OPENCLAW_OUTBOUND_BASE_URL=https://taotoken.net/api Environment=OPENCLAW_OUTBOUND_API_KEY=sk-你的Key Environment=OPENCLAW_OUTBOUND_MODEL=你的ModelID # 关键:显式清空代理变量,避免继承旧环境 Environment=HTTP_PROXY= Environment=HTTPS_PROXY= Environment=http_proxy= Environment=https_proxy= Environment=ALL_PROXY= [Install] WantedBy=default.target这里把五个代理变量全部显式置空,比“不写”更保险——因为 systemd 默认会继承systemctl --user调用时的环境,你之前 export 过的代理可能被带进来。显式置空等于强制覆盖。
改完单元文件后必须重载并重启:
systemctl --user daemon-reload systemctl --user restart openclaw-gateway systemctl --user status openclaw-gateway如果你更想用环境文件而不是写死在单元里,可以建~/.config/openclaw/gateway.env:
OPENCLAW_OUTBOUND_BASE_URL=https://taotoken.net/api OPENCLAW_OUTBOUND_API_KEY=sk-你的Key OPENCLAW_OUTBOUND_MODEL=你的ModelID HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= ALL_PROXY=然后在单元里用EnvironmentFile=%h/.config/openclaw/gateway.env引入。这样 Key 不进单元文件,改起来也方便。两种方式选一种,别混用,混用容易排查不清到底哪个生效。
4. 验证请求是否真的走通 TaoToken 通道
配置改完,别急着看日志,先用 curl 直接打 TaoToken 的 endpoint,确认网络层通不通。这一步能帮你把“网络问题”和“Gateway 配置问题”彻底分开。
curl -sS -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{ "model": "你的ModelID", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 16 }'如果返回里带choices字段,说明 Key、Model ID、网络三者都正常。如果返回 401,是 Key 问题;返回 404 或 model not found,是 Model ID 写错;连接被拒,那还是代理变量没清干净,回到第 5 节。
接着验证 Gateway 进程自己的环境变量,这是最关键的一步。先从 status 里拿到 PID:
openclaw gateway status # 记下 pid,比如 1528 cat /proc/1528/environ | tr '\0' '\n' | grep -i proxy这条命令拆开看:/proc/<pid>/environ是 Linux 虚拟文件系统里该进程的环境变量快照,变量之间用\0分隔,所以用tr '\0' '\n'转成换行,再grep -i proxy过滤。如果输出为空,恭喜,进程里没有代理残留。如果还看到http_proxy=http://127.0.0.1:7890,说明 systemd 那份环境还没清掉,回去检查单元文件或 EnvironmentFile。
再确认进程启动命令和监听端口:
cat /proc/1528/cmdline | tr '\0' ' ' ps -fp 1528 ss -ltnp | grep 18789cmdline看它到底用什么参数启动的,ss确认 18789 在听。最后触发一次真实请求,观察 Gateway 日志:
journalctl --user -u openclaw-gateway -f在另一个终端触发一次飞书 token 获取或你配置的任意出站动作,日志里应该看到请求发往taotoken.net而不是127.0.0.1:7890,并且返回 200。到这一步,ECONNREFUSED 就算真正解决了。
5. 本篇常见报错逐条排查对照
排查过程中会撞到几个典型报错,这里按真实日志逐条对照。
ECONNREFUSED 127.0.0.1:7890:最核心的那个。含义是代理端口没人听。先env | grep -i proxy看当前 shell,再cat /proc/<pid>/environ | tr '\0' '\n' | grep -i proxy看进程。两处都有就两处都清。临时清 shell 用:
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY但记住这只影响当前终端,systemd 托管的进程必须改单元文件或 EnvironmentFile 后daemon-reload+restart。
401 Unauthorized:请求打到 TaoToken 了,但 Key 不对。检查三件事:Key 有没有复制全(sk-开头那串)、有没有多余空格、是不是在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 里被删了。另外确认请求头是Authorization: Bearer sk-xxx,别写成x-api-key。
local proxy failed / proxy connect ECONNREFUSED:和第一个同源,只是措辞不同,说明请求确实尝试走本地代理。处理方式一样,清变量 + 关配置里的 proxy 开关。
reading 'choices' / Cannot read properties of undefined (reading 'choices'):这是拿到响应后解析失败,通常不是网络问题,而是返回体结构不对——比如 endpoint 写成了/api但实际要/api/v1/chat/completions,或者 Model ID 不存在导致返回了错误对象。先用第 4 节的 curl 确认返回结构,再对照接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里的路径。
OAuth / token 获取失败:如果 Gateway 里配了需要 OAuth 的外部服务,代理一断,OAuth 回调也走不通。这类问题优先确认出站 endpoint 是否已统一到 TaoToken,再检查回调地址是否可达。
systemd 重启后代理又回来:说明你只改了 shell 没改单元。用systemctl --user show openclaw-gateway -p Environment看 systemd 实际生效的环境,对照单元文件逐项核对。
如果排查半天还是不通,最干净的办法是重装服务:
openclaw gateway uninstall openclaw gateway install openclaw gateway start这会重新生成 systemd 单元和启动配置,把旧的继承环境一并清掉。重装后再按第 3 节把 TaoToken 三件套填回去。
6. 后续长期跑 Agent 的接入选择
问题解决之后,如果你只是偶尔验证一下模型,用模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 就够了,不用碰配置文件。但 OpenClaw Gateway 这种常驻服务,本质是长期跑 Agent 和自动化任务,出站请求量大、对稳定性敏感,这时候更建议走 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,额度和模型策略更适合持续调用。
接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里有各语言和各工具的完整示例,包括 Claude Code 的 Anthropic 兼容配置,照着改 Base URL、Key、Model ID 三件套即可。控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 可以看用量和 Key 管理,API Keys 页面负责增删 Key。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
最后留一个我踩过的坑:WSL 里 systemd 的 user 实例默认可能没开,如果systemctl --user报 “Failed to connect to bus”,先确认/etc/wsl.conf里[boot] systemd=true并wsl --shutdown重启。这个不解决,后面所有 systemd 操作都是空中楼阁。