Skip to content

fix(weixin): refuse to send when contextToken is missing (avoid silent-drop) - #247

Open
stwhwing wants to merge 2 commits into
Tencent:mainfrom
stwhwing:fix/weixin-silent-drop-contexttoken
Open

stwhwing wants to merge 2 commits into
Tencent:mainfrom
stwhwing:fix/weixin-silent-drop-contexttoken

Conversation

@stwhwing

@stwhwing stwhwing commented Aug 6, 2026

Copy link
Copy Markdown

fix(weixin): refuse to send when contextToken is missing instead of silent-drop

Summary

sendMessageWeixin / sendMessageItemWeixin / sendImageMessageWeixin /
sendVideoMessageWeixin / sendFileMessageWeixin currently only logger.warn
when opts.contextToken is missing, then send the request anyway. The WeChat
iLink backend silently discards messages sent without a (fresh) context
token — it returns HTTP 200 with no body and the message never reaches the
user. The function then returns a self-generated clientId as messageId,
so every caller up the stack (channel sendText/sendMedia, emitWeixinMessageSent)
reports success while nothing was delivered. This is a silent-failure bug.

This PR changes the 5 contextToken missing sites to throw a typed error
instead of warning-and-sending. Callers already wrap these in try/catch and
report delivery failure, so the throw is handled correctly.

Root cause

  • src/messaging/send.ts: if (!opts.contextToken) { logger.warn(...); }
    then await sendMessageApi(...) is still called.
  • sendMessageApi only rejects on transport/HTTP errors; a 200-with-empty-body
    "silent drop" is treated as success.
  • The function returns { messageId: clientId } where clientId is locally
    generated, not an acknowledgement from the server.
  • Result: command exits 0, all layers report success, user receives nothing.

Changes

In all 5 send functions, replace:

if (!opts.contextToken) {
  logger.warn(`<fn>: contextToken missing for to=${to}, sending without context`);
}

with:

if (!opts.contextToken) {
  const e = new Error(`[openclaw-weixin] <fn>: contextToken missing for to=${to} — refusing to send (silent-drop risk; see upstream issue)`);
  logger.error(e.message);
  throw e;
}

(<fn> is the respective function name.)

Testing

  • Verified the 5 contextToken missing branches are the only silent-drop paths
    in src/messaging/send.ts.
  • git apply --check passes cleanly on the published 2.4.6 source
    (packages/openclaw-weixin/src/messaging/send.ts).
  • Callers (channel.ts sendText/sendMedia) already try/catch and call
    emitWeixinMessageSent(success: false) on throw, so behaviour is correct.

Limitations / follow-up

  • This covers the missing contextToken case. It does not cover a
    token that is present but stale (not refreshed by a recent inbound
    message) — the backend still silently drops those. A more complete fix would
    also refuse/retry when the cached token age exceeds a threshold (e.g. read
    from the token store). Happy to follow up if maintainers want that scope.
  • The robust delivery path in practice is the WeCom (企业微信) webhook channel,
    which is unaffected by the iLink token lifecycle.

Reproduction & patch regeneration

How to reproduce the bug

  1. Configure an OpenClaw weixin account whose contextToken is missing or
    stale — i.e. a bot that has not received a recent inbound message from
    that user. The iLink backend only issues a fresh token per inbound message,
    so a quiet bot accumulates a missing/stale token.
  2. Trigger any outbound send: openclaw message send, a cron job with
    deliver: weixin, or an @bot reply through the weixin channel.
  3. Observe: the CLI exits 0, emitWeixinMessageSent(success: true) fires, yet
    the recipient receives nothing. The backend returned HTTP 200 with an
    empty body and silently dropped the message. That is the silent-failure bug.

How to regenerate this patch for a new version

The fix is mechanical (5 identical warnthrow sites), so a helper script
is provided for regression testing against future releases:

# from the repo root of a fork of @tencent-weixin/openclaw-weixin
python3 gen_patch.py packages/openclaw-weixin/src/messaging/send.ts
# -> writes packages/openclaw-weixin/src/messaging/pr-patch-send-contexttoken.patch

gen_patch.py rewrites the 5 contextToken missing branches and emits a
unified diff with LF line endings (so it applies cleanly on Linux/macOS).
It is a regeneration/regression helper only — not the reproduction steps
above. The script accepts the path to send.ts as its only argument; if it
reports a count other than 5, the source version has drifted and the patch
needs a manual review.

Verify before opening the PR

cd packages/openclaw-weixin
git apply --check pr-patch-send-contexttoken.patch   # must print nothing / exit 0

Checklist

  • git apply --check passes on 2.4.6
  • Only the 5 silent-drop sites changed; no behaviour change for valid tokens
  • Callers handle the throw correctly

When contextToken is absent the backend returns 200 but silently drops
the message, so callers believe delivery succeeded (a false success).
Explicitly throw on missing contextToken so the caller's try/catch can
report a typed delivery failure instead of a silent drop.

See P1-6 / openclaw issue: external Weixin channel outbound silent-success.

Limitations: stale (present-but-expired) tokens are still accepted by the
backend and silently dropped; refreshing token freshness is a separate fix.
@NewFuture

Copy link
Copy Markdown

感谢你定位缺少 contextToken 时“请求看似成功、消息实际未送达”的问题。社区版当前 main 中,文本、结构化 item、图片、视频和文件这 5 个发送入口仍然只是告警后继续发送,因此该问题尚未解决。

如果你愿意,欢迎由你基于社区仓库当前 main 分支提交适配 PR:

https://github.com/NewFuture/openclaw-weixin

迁移时请按当前错误传播和事件上报路径处理:5 个入口都应在调用后端前明确失败,并分别增加回归测试;同时需要一个携带有效 token 的反例,证明正常发送不受影响。日志和错误信息请不要包含原始 token、完整目标 ID 等敏感值。这样可以让 channel、普通回复和媒体发送都得到真实的失败结果,而不是继续上报假成功。

贡献指南:
https://github.com/NewFuture/openclaw-weixin/blob/main/CONTRIBUTING.md

说明:NewFuture/openclaw-weixin 是社区维护发行版,并非腾讯/微信官方版本。社区 PR 会保留你的原作者署名,并链接本 PR 说明来源和适配差异。

…t-drop)

Adapted from Tencent#247 (authored by stwhwing).
Community edition (NewFuture/openclaw-weixin) still had 5 send entry
points that warned-then-sent without contextToken, causing iLink to
return HTTP 200 with empty body and silently drop the message while
the caller saw a fake success.

Changes:
- send.ts: throw on missing contextToken in all 5 send entry points
  (sendMessageWeixin, sendMessageItemWeixin, sendImageMessageWeixin,
  sendVideoMessageWeixin, sendFileMessageWeixin); error messages carry
  no raw token or full recipient id (redacted via redactToken).
- send.test.ts: flip 4 existing 'no throw' tests to assert throw +
  backend not called; add focused regression + valid-token counterexample.
- CHANGELOG: add Unreleased entry (zh + en); no version bump.

Verified: send.test.ts 23/23, tsc clean, biome 0 errors.
stwhwing added a commit to stwhwing/openclaw-weixin-nf that referenced this pull request Aug 10, 2026
…t-drop)

Adapted from Tencent/openclaw-weixin#247 (authored by stwhwing).
Community edition (NewFuture/openclaw-weixin) still had 5 send entry
points that warned-then-sent without contextToken, causing iLink to
return HTTP 200 with empty body and silently drop the message while
the caller saw a fake success.

Changes:
- send.ts: throw on missing contextToken in all 5 send entry points
  (sendMessageWeixin, sendMessageItemWeixin, sendImageMessageWeixin,
  sendVideoMessageWeixin, sendFileMessageWeixin); error messages carry
  no raw token or full recipient id (redacted via redactToken).
- send.test.ts: flip 4 existing 'no throw' tests to assert throw +
  backend not called; add focused regression + valid-token counterexample.
- CHANGELOG: add Unreleased entry (zh + en); no version bump.

Verified: send.test.ts 23/23, tsc clean, biome 0 errors.
NewFuture added a commit to NewFuture/openclaw-weixin that referenced this pull request Aug 10, 2026
…t-drop) (#60)

* fix(weixin): refuse to send when contextToken is missing (avoid silent-drop)

Adapted from Tencent/openclaw-weixin#247 (authored by stwhwing).
Community edition (NewFuture/openclaw-weixin) still had 5 send entry
points that warned-then-sent without contextToken, causing iLink to
return HTTP 200 with empty body and silently drop the message while
the caller saw a fake success.

Changes:
- send.ts: throw on missing contextToken in all 5 send entry points
  (sendMessageWeixin, sendMessageItemWeixin, sendImageMessageWeixin,
  sendVideoMessageWeixin, sendFileMessageWeixin); error messages carry
  no raw token or full recipient id (redacted via redactToken).
- send.test.ts: flip 4 existing 'no throw' tests to assert throw +
  backend not called; add focused regression + valid-token counterexample.
- CHANGELOG: add Unreleased entry (zh + en); no version bump.

Verified: send.test.ts 23/23, tsc clean, biome 0 errors.

* fix(weixin): align missing-token callers

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: New Future <6290356+NewFuture@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants