Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ This project follows the [Keep a Changelog](https://keepachangelog.com/) format.

### Fixed

- **`iLink-App-Id` / `iLink-App-ClientVersion` headers were empty / `0` in production.** `readPackageJson` resolved `package.json` via a fixed `../../` from `import.meta.url`, but the TypeScript build (with `index.ts` plus `src/**/*.ts` in `tsconfig.include`) emits `dist/src/api/api.js` (extra `src/` segment), so the resolved path landed on the non-existent `dist/package.json` and the catch returned `{}`. Replaced with a walk-up that searches for the plugin's own `package.json` (validated by `name` containing `openclaw-weixin` or by the presence of `ilink_appid`), tolerating both dev (`src/api/`) and built (`dist/src/api/`) layouts. Adds tests in `src/api/api.test.ts` covering the compiled layout, dev layout, nested `node_modules/<dep>/package.json` shadowing, missing manifest, and malformed manifest.
- **`openclaw channels login` exited non-zero when the bot was already bound to this OpenClaw**, which caused automated installers (e.g. `openclaw-weixin-installer`) to report a misleading "首次连接未完成" message and continue past a successful state. The QR poller now returns `alreadyConnected: true` for the server's `binded_redirect` status, and `auth.login` in `channel.ts` treats it as a successful no-op (no save, no throw) so the CLI exits cleanly.
- **`iLink-App-Id` / `iLink-App-ClientVersion` headers were empty / `0` in production.**
- **`openclaw channels login` exited non-zero when the bot was already bound to this OpenClaw.**
- **`getUpdates` long-poll intermittently enters 30s backoff after `UND_ERR_HEADERS_TIMEOUT`.** The Node.js undici `HeadersTimeoutError` is a normal long-poll boundary event (the server held the connection waiting for messages and none arrived), but the monitor's catch block counted it as a hard failure. After 3 consecutive occurrences the channel backed off for 30 seconds, making the Weixin channel appear unavailable. The catch block now treats `UND_ERR_HEADERS_TIMEOUT` as a soft failure: logged at debug level, retried after 2 seconds, and not counted toward the backoff counter. Other network errors (connection refused, DNS failure, TLS error, etc.) continue to trigger the hard-failure path.

## [2.4.2] - 2026-05-07

Expand Down
5 changes: 3 additions & 2 deletions CHANGELOG.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

### 修复

- **`iLink-App-Id` / `iLink-App-ClientVersion` 请求头在生产环境为空 / `0`。** `readPackageJson` 用固定的 `../../` 从 `import.meta.url` 推算 `package.json`,但 TypeScript 构建(`tsconfig.include` 同时包含 `index.ts` 和 `src/**/*.ts`)实际产物是 `dist/src/api/api.js`(多出一层 `src/`),导致解析到不存在的 `dist/package.json`,catch 返回 `{}`。改为从当前模块所在目录向上逐级查找,并通过 `name` 包含 `openclaw-weixin` 或存在 `ilink_appid` 字段来确认是本插件自己的 `package.json`,同时兼容开发态(`src/api/`)和发布态(`dist/src/api/`)布局。`src/api/api.test.ts` 新增 5 个用例覆盖编译产物布局、开发布局、途经 `node_modules/<dep>/package.json` 不被误识别、找不到时返回 `{}`、坏 JSON 容错继续向上查找。
- **`openclaw channels login` 在 "已连接过此 OpenClaw" 场景下被误判为失败。** 服务端返回 `binded_redirect` 时本地凭据其实仍有效,但旧逻辑返回 `connected: false`,`channel.ts` 的 `auth.login` 据此 `throw`,CLI 非零退出,导致 `openclaw-weixin-installer` 等自动化脚本误打印"首次连接未完成"。`WeixinQrWaitResult` 新增 `alreadyConnected` 字段,QR 轮询在 `binded_redirect` 时置为 `true`;`auth.login` 据此仅记录消息、不抛错,CLI 以 0 退出。
- **`iLink-App-Id` / `iLink-App-ClientVersion` 请求头在生产环境为空 / `0`。**
- **`openclaw channels login` 在 "已连接过此 OpenClaw" 场景下被误判为失败。**
- **`getUpdates` 长轮询因 `UND_ERR_HEADERS_TIMEOUT` 间歇性进入 30 秒退避。** Node.js undici 的 `HeadersTimeoutError` 是正常的长轮询边界事件(服务器保持连接等待消息,期间无消息到达),但 monitor 的 catch 块将其计入硬故障。连续 3 次后触发 30 秒退避,导致微信通道表现不可用。现将其视为软失败:仅记录 debug 日志,2 秒后重试,不计入退避计数器。其他网络错误(连接拒绝、DNS 失败、TLS 错误等)仍走硬故障路径不变。

## [2.4.2] - 2026-05-07

Expand Down
14 changes: 14 additions & 0 deletions src/monitor/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,20 @@ export async function monitorWeixinProvider(opts: MonitorWeixinOpts): Promise<vo
aLog.info(`Monitor stopped (aborted)`);
return;
}
// UND_ERR_HEADERS_TIMEOUT is a normal long-poll timeout — the server
// held the connection waiting for messages and none arrived. Treat it
// as a soft failure: don't count toward the 3-strike backoff.
if (
err instanceof TypeError &&
err.message === "fetch failed" &&
(err as any).cause?.code === "UND_ERR_HEADERS_TIMEOUT"
) {
aLog.debug(
`getUpdates: long-poll headers timeout (normal), retrying in ${RETRY_DELAY_MS}ms`,
);
await sleep(RETRY_DELAY_MS, abortSignal);
continue;
}
consecutiveFailures += 1;
errLog(
`weixin getUpdates error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`,
Expand Down