AI agent 编排工具 Goose Desktop 持续出现 404 错误的长期未解决 Issue
本文最后更新于 2026年2月22日 晚上
背景描述
这几天我在 Windows(以及 Linux)上用 Goose Desktop GUI 配 Azure OpenAI,撞上了一个很离谱的现象:GUI 里填的 LLM 地址/配置看起来完全正确,但一用就报 404。
1 | |
更离谱的是——同样的 Azure OpenAI 服务:
- 用
curl直接调用没问题 - 用 VScode 直接连也没问题
- 装了 Goose CLI 之后,Desktop 反而突然好了,还能看到 CLI 刚跑出来的 session
- 换多台机器,不同操作系统验证过
这整件事看起来是:Goose Desktop 本地的 goosed 后端无法正确的加载。
相关 Issue(同类症状):block/goose#3571
关键事实:Desktop 不是“GUI 直连云端”,而是 “GUI → 本地 goosed → 云端”
Goose Desktop 运行时会在本机拉起一个 Rust 后端进程 goosed,GUI 只是通过 127.0.0.1:<port> 调它的 API,再由 goosed 去请求供应商。
所以“看起来像 LLM 404”的错误,至少要拆成三类看:
- 本地 goosed API 返回的 4xx/5xx(例如鉴权、路由、版本不匹配)
- goosed 转发到供应商后,供应商返回的 4xx/5xx
- goosed 在后台做“拉模型列表/探活”失败(跟真正发
chat/completions不是一回事)
试验过程
1)Desktop 里“看起来任何 provider 都 404”
GUI 提示经常是:
1 | |
日志里则会看到类似(注意这个位置经常出现在 Desktop 的 main.log 里,因为它会把 goosed 的 stderr 转存进去):
1 | |
这跟 issue block/goose#3571 的描述非常像:同一套 key/模型用 curl 能通,但 Desktop 里持续 404。
2)curl / PowerShell 直连 Azure:服务是好的
以 Azure OpenAI 为例,我这边 endpoint 大概长这样:
1 | |
可以直接 200 并返回回复。
结论:Azure chat/completions 本身是通的。
3) 省略其中 AI 带着我逛了至少 4h 的完全方向都错误的过程
根本原因
不知道是不是像我这样用自带 LLM 的人太少,半年都没人定位到根本原因(Root Cause);
三天前维护者还说拖太久了,直接把 issue 关了,实在离谱。
https://github.com/block/goose/issues/3571#issuecomment-3871651227
上面是我找到的绕过办法。本质原因是:Desktop 在写入 config.yml 时比 CLI 少写了 GOOSE_PROVIDER 和 GOOSE_MODEL 两个字段,导致程序找不到 MODEL;
而选择 MODEL 时又必须先验证成功才能把配置写回文件——于是形成死循环,永远连不上。
因此只有先安装 CLI 并在其中配好,才能正常用。
更新:官方在 v1.24.0(2026-02-12)中修复了该问题。具体改动见 PR fix(providers): Azure OpenAI model listing 404 during configure #7034,Release 说明见 v1.24.0 · block/goose。升级到 v1.24.0 或更高版本后,Desktop 配置 Azure 时不应再出现“拉模型列表 404”导致的死循环。
开盖研究
这一章开始“开盖”——不再盯着 GUI 的报错,而是把 goosed 从源码编出来,直接用 API 证明它到底有没有成功连到 Azure。
1)在 Windows 上把后端编出来(release)
由于从来也没用过 Rust,且有近 20年 没在 Windows 上运行编译型语言,以下路径也均为 AI建议 先走试试。
我用的路径是“纯 Windows + pwsh + MSVC toolchain”,核心是两件事:
- Rust 工具链(rustup/cargo)
- VS Build Tools(提供
link.exe)
然后编后端:
1 | |
产物默认在:
1 | |
2)启动 goosed,并固定一个 X-Secret-Key
goosed 除 /status 外的大多数 API 都需要 X-Secret-Key。本地跑建议固定一个:
1 | |
验证 health:
1 | |
3)验证:goosed 是否确实读取了 config.yaml 里的 Azure 配置
用 goosed 的 /config 直接读(需要 X-Secret-Key):
1 | |
这一步能确认:配置确实被 goosed 读取到了。
4)最关键:验证 goosed 能否真实调用 Azure(不是 /status 那种空壳)
我用的是一条“完整链路”:
POST /agent/start创建 sessionPOST /agent/update_provider设置azure_openai + gpt-4.1POST /reply发一条消息,看 SSE 是否出现Message事件
为什么这能作为“硬证据”?
/reply是真正会触发agent.reply()的路径- provider 会走到
OpenAiCompatibleProvider的chat/completions - 如果没连到供应商,通常会返回
Error事件或直接 5xx
后端链路代码入口:
/replyhandler:crates/goose-server/src/routes/reply.rs
关键片段(摘录):
1 | |
而真正发出 chat/completions 的位置是:
crates/goose/src/providers/openai_compatible.rs
关键片段(摘录):
1 | |
我实际用来验证的 PowerShell(可复用)
如果你想完整照抄一遍验证,可以参考文末附录里的 PowerShell 脚本(负责创建 session、设置 azure_openai + gpt-4.1 并调用 /reply,确认 SSE 里出现 Message 事件)。
5)Azure 列模型 404:根因与修复
根因就是列模型时 URL 拼错了:旧代码把 deployment 塞进了所有请求共用的 host(如 endpoint/openai/deployments/gpt-4.1),导致列模型也发成 GET .../deployments/gpt-4.1/models,而 Azure 只认 GET .../openai/models,所以 404。
官方修法(PR #7034):把 URL 拼对——列模型用 {endpoint}/openai/models,发对话时才在路径前加 deployments/{name}/(见 openai_compatible.rs 的 completions_prefix),各用各的路径,404 消失。
参考实现(我本地的 patch 思路)
说明:下面是“修复方向”的代码形态。你也可以把它理解为:Azure 既然不支持
/openai/models,那就别去打它,返回空列表让 UI 手输 deployment name。
1)服务端:Azure 直接返回空列表,避免触发 GET /openai/models
文件:crates/goose-server/src/routes/config_management.rs
1 | |
2)Provider 元数据:允许 UI 手输“不在列表里的 model”(Azure 语义上就是 deployment name)
文件:crates/goose/src/providers/azure.rs
1 | |
与 PR #7034 实际改动的对比
下面用三张流程图说明:修复前为什么 404、当时推测的改法、官方实际改法。不涉及任何前端知识,只看“请求往哪儿发、结果是什么”即可。
① 修复前:为什么配置时会 404?
当时代码把「deployment 名」塞进了所有请求的共用地址里,导致「列模型」和「发对话」都用了同一个错误的基础路径:
flowchart LR
subgraph 旧代码的共用地址
A["host = endpoint/openai/deployments/gpt-4.1"]
end
subgraph 列模型时
B["GET host + 'models'"]
C["即 GET .../deployments/gpt-4.1/models"]
D["Azure 不支持 → 404"]
B --> C --> D
end
subgraph 发对话时
E["POST host + 'chat/completions'"]
F["即 POST .../deployments/gpt-4.1/chat/completions"]
G["Azure 支持 → 200"]
E --> F --> G
end
A --> B
A --> E
一句话:列模型本不该带 deployments/xxx,但旧代码把 host 设成了带 deployment 的地址,所以「列模型」请求发错地方,404;配置页就卡死。
② 当时推测的改法(应用层兜底)
不动 URL 构造,在「列模型」这一步直接不请求 Azure,返回空列表,让用户在界面里自己填 deployment 名;发对话时仍然用原来的路径(带 deployment):
flowchart LR
subgraph 列模型
A1["Desktop 要列模型"]
B1["服务端发现是 azure_openai"]
C1["直接返回空列表 []"]
D1["不请求 Azure → 不会 404"]
A1 --> B1 --> C1 --> D1
end
subgraph 填配置
E1["用户在界面手填 deployment 名"]
end
subgraph 发对话
F1["POST .../deployments/xxx/chat/completions"]
G1["和以前一样 → 200"]
F1 --> G1
end
D1 --> E1
E1 --> F1
一句话:不修 URL,而是「列模型」时对 Azure 直接不请求、返回空,让用户手填模型名,从而绕过 404。
③ 官方 PR #7034 的改法(修对 URL)
把「共用地址」和「仅对话用的前缀」拆开:列模型用「不带 deployment」的地址,发对话时才在路径前加上 deployments/xxx/:
flowchart LR
subgraph 新代码的地址
H["host = endpoint/openai<br/>(不再带 deployment)"]
I["completions_prefix = 'deployments/gpt-4.1/'<br/>(只用于发对话)"]
end
subgraph 列模型
J["GET host + 'models'"]
K["即 GET .../openai/models"]
L["不带 deployment → Azure 可接受 → 200"]
J --> K --> L
end
subgraph 发对话
M["POST host + completions_prefix + 'chat/completions'"]
N["即 POST .../openai/deployments/gpt-4.1/chat/completions"]
O["和以前一样 → 200"]
M --> N --> O
end
H --> J
H --> I
I --> M
一句话:列模型用 .../openai/models,发对话用 .../openai/deployments/xxx/chat/completions,各用各的路径,404 消失。
小结
| 维度 | 当时推测 | PR #7034 实际改动 |
|---|---|---|
| 根因 | 列模型时请求了 Azure 不支持的路径 → 404 | 一致:deployment 路径被错误地加到了「列模型」的请求上 |
| 实现 | 在 config_management.rs 里对 azure_openai 直接返回 [],不再请求 Azure;provider 加 with_unlisted_models() |
不改 config 层,改 URL 构造:host 只保留 {endpoint}/openai,仅在发 chat/completions 时加上前缀 deployments/{name}/(见 openai_compatible.rs 新增的 completions_prefix) |
| 结果 | 列模型不再打 Azure,404 消失 | 列模型请求变为 GET {endpoint}/openai/models(与 openai-python SDK 行为一致),不再带 deployment 路径,404 消失 |
两种做法都能打破配置死循环;官方做法是把 URL 写对,推测做法是在应用层不请求、让用户手填。文中前面的技术细节看不懂也没关系,只要看懂上面三张图:① 以前错在哪儿 → ② 我们想的办法 → ③ 官方修的办法,就够用了。