{"record":{"id":"aa6f305a377acd26","repo":"BigPizzaV3/CodexPlusPlus","slug":"context-token","errorCode":null,"errorMessage":"微信回复缺少 context_token","messagePattern":"微信回复缺少 context_token","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/connect/weixin.rs","lineNumber":256,"sourceCode":"        if updates.ret != 0 || updates.errcode != 0 {\n            bail!(\n                \"微信长轮询被拒绝：ret={} errcode={} {}\",\n                updates.ret,\n                updates.errcode,\n                updates.errmsg\n            );\n        }\n        Ok(updates)\n    }\n\n    pub async fn send_text_chunks(\n        &self,\n        to_user_id: &str,\n        text: &str,\n        context_token: &str,\n    ) -> anyhow::Result<()> {\n        if context_token.trim().is_empty() {\n            bail!(\"微信回复缺少 context_token\");\n        }\n        for chunk in chunk_text(text, MAX_REPLY_CHARS) {\n            self.send_text(to_user_id, &chunk, context_token).await?;\n            tokio::time::sleep(Duration::from_millis(100)).await;\n        }\n        Ok(())\n    }\n\n    async fn send_text(\n        &self,\n        to_user_id: &str,\n        text: &str,\n        context_token: &str,\n    ) -> anyhow::Result<()> {\n        let request_body = json!({\n            \"msg\": {\n                \"from_user_id\": \"\",\n                \"to_user_id\": to_user_id,","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/f2074595a281bc057525c748175c8eb9805b0673/crates/codex-plus-core/src/connect/weixin.rs#L238-L274","documentation":"send_text_chunks refuses to send when context_token is empty or whitespace: WeChat replies must be authorized by the context token that arrived with the inbound message, and a send without one would be rejected upstream. This is a caller-side contract check, not a server error.","triggerScenarios":"Calling send_text_chunks with a blank token, for example constructing WeixinMessage manually or a reply path where message.context_token was never populated. The built-in loop skips inbound messages lacking a token before processing, so hitting this usually means custom code.","commonSituations":"Custom integrations replying outside the standard loop; refactors that drop the token field; test fixtures with messages missing context_token.","solutions":["Thread the inbound message context_token through to every reply","Guard upstream the way run_weixin_connect does: skip or ack messages whose context_token is empty","Populate context_token in test fixtures"],"exampleFix":"// before\nclient.send_text_chunks(&user, &reply, token).await?;\n// after: guard the token like the connector loop guards inbound messages\nlet token = token.trim();\nif token.is_empty() { return Ok(()); }\nclient.send_text_chunks(&user, &reply, token).await?;","handlingStrategy":"validation","validationCode":"let token = message.context_token.trim();\nif token.is_empty() {\n    // mirror the connector loop: skip or ack instead of attempting a send\n    return Ok(());\n}\nclient.send_text_chunks(&message.from_user_id, &reply, token).await?;","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass the inbound message context_token into every reply","Skip inbound messages with empty tokens, as run_weixin_connect does","Populate context_token in test fixtures"],"tags":["weixin","validation","context-token","reply"],"backgroundTag":"missing-required-argument","analyzedSha":"f2074595a281bc057525c748175c8eb9805b0673","analyzedAt":"2026-08-23T12:52:24.489Z","contentChangedAt":"2026-08-23T12:52:24.489Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}