目录

WorkBuddy 对 DSH 下手了:400 报错(Illegal API invocation from an unapproved channel)的真相与解法

一句话结论:这个报错跟「渠道」一点关系都没有。上游在对 system prompt 做关键词内容风控,命中 DeepSeek Harness 就拒。解法是在两个词之间插一个看不见的零宽空格 —— 语义不变、上下文不丢、完全无损。


那天晚上发生了什么

如果你也在用 DeepSeek Harness(DSH)通过 WorkBuddy 渠道跑模型,某天晚上大概会突然看到这个:

buddy: 请求被拦截,请重新发送
Illegal API invocation from an unapproved channel

HTTP 400,上游 code 11128(有时是 11140)。

不是偶发,是全部请求都挂。换账号、重启、重装插件,全都没用。

社区里当时的猜测基本都指向「渠道被封了」「账号被风控了」—— 因为报错文案本身就是这么写的:*unapproved channel*,未获批准的渠道。

但这个文案在骗你。


60 秒自证:这不是渠道问题

不用信我,你自己两分钟就能验证。

用同一个 token,打两次请求,只改 system prompt 里的一个词:

system prompt 内容结果
You are an AI agent powered by DeepSeek Harness.❌ HTTP 400 / 11128
You are an AI agent.✅ HTTP 200

如果符合这个规律,答案就已经出来了 —— 跟渠道、账号、客户端身份、请求头,都没有关系。

⚠️ 一个容易误判的细节

关键词要配合请求体足够大(大约 >1KB)才会稳定触发。

所以短 prompt 即使含关键词也会被放过 —— 这是最容易让人误判成「偶发故障」「网络抽风」的地方。用最小请求去测,会测不出来。


我已经排除过的假设(别重复踩坑)

这些都是我逐项实测排除的,列出来省你的时间:

假设实测结果
账号被风控换账号同样报错
域名不对(copilot.tencent.com vs www.workbuddy.ai)国内站国际站都试过,都失败
客户端身份不对(WorkBuddy vs CodeBuddy)两种都试过
缺请求头(13 个都补齐)仍然失败
reasoning_effort 值非法单独测也返回 200
max_tokens 超限调小也一样
工具数量太多(316 个)减到少量仍然失败
网络 / 代理直连也一样
并发过高12 并发全成功过

全都不是原因。


真因:关键词内容风控

上游在请求发出前,会对 messages 做一次关键词匹配。

命中的规则,是 system prompt 里的这一段身份说明:

You are an AI agent powered by DeepSeek Harness.

只要出现 DeepSeek Harness 这个词组,请求直接被拒 —— 报的还是那句误导性的 "unapproved channel"。

至于为什么要拦这个词,我不做推测。对使用者来说,重要的是怎么在不损失功能的前提下绕过去。


解法:插一个看不见的字符

在 DeepSeek 和 Harness 之间,插入一个零宽空格 U+200B:

// 改前
"You are an AI agent powered by DeepSeek Harness."

// 改后(中间那个字符是 U+200B,肉眼看不见)
"You are an AI agent powered by DeepSeek\u200B Harness."

就这么简单。为什么有效:

  • 模型看到的语义完全不变 —— 零宽空格不参与分词,模型读到的是同一个意思
  • 上游的子串匹配不再命中 —— DeepSeek Harness 这个连续字符串不存在了
  • 无损 —— 没有裁剪、没有改写、没有丢上下文、没有降级

实测数据

用真实的 DSH 请求形状测(316 个 tools + effort=max + max_tokens=128000):

原文    → ❌ 0/5 全部失败
中和后  → ✅ 5/5 全部成功

⚠️ 一个必须注意的坑:路径不能被改写

这个坑我踩过,会导致工具调用全部失败。

DSH 的运行时上下文里包含真实文件路径,比如:

D:\10_COD\DeepSeek Harness\resources\app.asar\dsh\

如果你无脑全局替换,路径会变成:

D:\10_COD\DeepSeek Harness\resources\...   ← 中间插了零宽字符

模型会把这个坏路径传给工具,然后一切文件操作都失败。而且因为零宽字符肉眼不可见,你会盯着屏幕看不出任何异样,只会觉得「文件怎么突然不存在了」。

正确做法是给匹配器加前后负向断言,跳过路径语境:

/(?<![\\/])(DeepSeek)[ \u00A0]+(Harness)(?![\\/])/gi

(?<![\\/]) 表示前面不能是斜杠,(?![\\/]) 表示后面不能是斜杠 —— 这样路径里的 DeepSeek Harness 就会被完整保留。


把它做成了插件

手工改 prompt 不现实(每次都要改、还容易漏)。所以我把它写成了 DSH 插件,叫 wb400修复。

装在 DSH 的「设置」页里,4 个开关:

开关默认作用
总开关开关掉就完全恢复原始请求,一个字节都不改
中和身份关键词开上面说的零宽空格替换
补首条 system开首条消息不是 system 时自动补一条(11128 的另一个成因)
隐藏品牌词关更激进:连 DeepSeek 也打散

改完即时生效,不用重启。

它对你的代码做了什么

在 pack.js 里插入一行:

;globalThis.__wbLexicon?.rewrite?.(messages);

位置在 workbuddy 的 messages 组装完成、发出去之前。

  • 只影响 WorkBuddy / CodeBuddy 两个 provider 的出站请求
  • 钩子未安装时可选链短路,行为与未打补丁完全一致
  • pnpm 重装会冲掉这行 —— 插件启动时自动自愈重打

快速安装

前置条件:

项要求
DSH 桌面版已在用
dsh-our-free-model已安装并登录至少一个 WorkBuddy / CodeBuddy 账号
Node.js^22.19.0 或 >=24.0.0

不需要:官方客户端后台常驻、额外 API Key、管理员权限。

# 或者直接在 DSH 里说「把 <目录绝对路径> 装成插件」

装完重启一次 DSH,在「设置」里就能看到 wb400修复。

仓库:https://github.com/balongTTY/dsh-workbuddy-fix


另一个相关插件

如果你还想知道每次请求烧了多少积分,配套的计量面板在:

https://github.com/balongTTY/dsh-workbuddy-meter

两个插件可以同时装,而且开关是联动的(共享同一个状态文件 ~/.dsh/workbuddy-meter/lexicon.json)—— 在任一边改开关,另一边立即跟随。

也可以只装一个:

  • 只装 fix → 有修复功能,没有计量面板
  • 只装 meter → 两者都有(meter 内置了同样功能)
  • 都装 → 修复逻辑一致,开关双向同步

错误码速查

顺手做个对照表,方便搜到的人直接定位:

上游 code报错文案真实原因这个插件能修吗
11128Illegal API invocation from an unapproved channel上游按关键词拦 system prompt✅ 能
11128first message is not system prompt首条不是 system 角色✅ 能(补首条 system 开关)
11140安全策略拦截同一错误族✅ 能
11133integer_below_min_valuemax_tokens 太小❌ 调大 max_tokens
6004usage exceeds frequency limit上游限流❌ 换账号或等冷却
——EMPTY_RESPONSE(有思考无正文)推理模型把 max_tokens 吃光❌ max_tokens 设 ≥4096

已知边界(诚实说明)

  • 只对本机 OFM 进程内的请求生效;服务器上做反代的流量要单独装
  • 上游如果换了关键词(不再拦 DeepSeek Harness),这个插件就不需要了 —— 关掉总开关即可
  • 钩子插入点依赖 OFM 的内部结构;OFM 大版本升级后若锚点消失,面板会显示「未打钩子」并给出原因
  • 本文不推测上游为什么要拦这个词,只解决「怎么不受影响」

小结

报错Illegal API invocation from an unapproved channel / code 11128
文案说渠道未获批准
真相上游对 system prompt 做关键词风控,命中 DeepSeek Harness
解法插入零宽空格 U+200B,语义不变
注意文件路径不能被改写,匹配器要跳过路径语境
工具wb400修复

如果你的现象跟上面不一样(比如短 prompt 也报错、或者换了模型就好),欢迎到仓库开 issue,把请求贴出来一起看。


后来

这个工具修好之后,我在改它的过程中把宿主搞崩了四次 —— 同一个 bug 的两个副本,第三次是我第一次「修好」之后两天又崩的。

那件事的复盘在另一篇:我写的 DSH 插件把宿主搞崩了四次,最后发现是同一个 bug 的两个副本

如果你也写包装 globalThis.fetch 的插件,那篇可能比这篇更有用 —— 里面有 RangeError: Maximum call stack size exceeded 的完整根因链,以及「测试全绿但功能坏了」是怎么发生的。