{"record":{"id":"986f9d39cc167731","repo":"chenhg5/cc-connect","slug":"weixin-missing-context-token-for-peer-q","errorCode":null,"errorMessage":"weixin: missing context_token for peer %q","messagePattern":"weixin: missing context_token for peer %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weixin/media_outbound.go","lineNumber":50,"sourceCode":"}\n\ntype cdnUploadedRef struct {\n\tdownloadParam string\n\taesKey        []byte\n\tcipherSize    int\n\trawSize       int\n}\n\nfunc (p *Platform) resolveReplyContext(replyCtx any) (*replyContext, error) {\n\trc, ok := replyCtx.(*replyContext)\n\tif !ok || rc == nil {\n\t\treturn nil, fmt.Errorf(\"weixin: invalid reply context\")\n\t}\n\tif strings.TrimSpace(rc.contextToken) == \"\" {\n\t\trc.contextToken = p.getContextToken(rc.peerUserID)\n\t}\n\tif strings.TrimSpace(rc.contextToken) == \"\" {\n\t\treturn nil, fmt.Errorf(\"weixin: missing context_token for peer %q\", rc.peerUserID)\n\t}\n\treturn rc, nil\n}\n\nfunc (p *Platform) uploadToWeixinCDN(ctx context.Context, to string, plaintext []byte, mediaType int, label string) (*cdnUploadedRef, error) {\n\tif len(plaintext) == 0 {\n\t\treturn nil, fmt.Errorf(\"weixin: %s: empty payload\", label)\n\t}\n\tif strings.TrimSpace(p.cdnBaseURL) == \"\" {\n\t\treturn nil, fmt.Errorf(\"weixin: cdn_base_url is empty\")\n\t}\n\trawSize := len(plaintext)\n\taesKey := make([]byte, 16)\n\tif _, err := rand.Read(aesKey); err != nil {\n\t\treturn nil, fmt.Errorf(\"weixin: %s: aes key: %w\", label, err)\n\t}\n\tfilekey := randomHex(16)\n\treq := getUploadURLRequest{","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weixin/media_outbound.go#L32-L68","documentation":"After type-checking the reply context, resolveReplyContext tries to fill rc.contextToken from getContextToken(rc.peerUserID). If both the supplied token and the cached lookup are blank, media upload cannot be tied to a valid WeChat session, so it fails with this formatted message naming the peer.","triggerScenarios":"Sending an image/file/audio where the replyContext has an empty contextToken AND the platform has no stored context token for that peerUserID (e.g. token expired, never recorded, or in-memory cache lost after restart).","commonSituations":"Proactively sending media to a user without a prior inbound message to anchor the context_token; bot restarted losing cached tokens; peer context expired on WeChat's side.","solutions":["Set contextToken explicitly in the replyContext from the most recent inbound message from that peer.","Have the user send any message to the bot first so getContextToken can cache a valid token, then retry the media send.","Persist context tokens across restarts so media sends after a restart still have a token."],"exampleFix":"// before\nrc := &replyContext{peerUserID: peer} // missing context_token\n// after\nrc := &replyContext{peerUserID: peer, contextToken: lastInboundToken(peer)}","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(rc.contextToken) == \"\" && p.getContextToken(rc.peerUserID) == \"\" { return errors.New(\"cannot send media: no context token for peer\") }","typeGuard":null,"tryCatchPattern":"if err := p.SendImage(ctx, rc, img); err != nil && strings.Contains(err.Error(), \"missing context_token\") { requestUserToMessageFirst(peer) }","preventionTips":["Capture context_token from every inbound message immediately","Persist tokens to disk so restarts don't lose them","Only send media to peers with a known recent inbound message"],"tags":["weixin","media","context-token","session"],"backgroundTag":"missing-required-argument","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}