{"record":{"id":"2abd52ca8761467d","repo":"sipeed/picoclaw","slug":"wecom-upload-requires-1-d-chunks-got-d","errorCode":null,"errorMessage":"wecom upload requires 1-%d chunks, got %d","messagePattern":"wecom upload requires 1-(.+?) chunks, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":692,"sourceCode":") (*wecomOutboundMedia, error) {\n\t_ = ctx\n\n\tcontentType = detectLocalWeComContentType(localPath, contentType)\n\tfilename = ensureWeComOutboundFilename(filename, localPath, contentType)\n\n\tdata, err := os.ReadFile(localPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read media file: %w\", err)\n\t}\n\tsize := int64(len(data))\n\tkind := outboundWeComMediaKind(part.Type, filename, contentType, size)\n\tif kind == \"\" {\n\t\treturn nil, fmt.Errorf(\"unsupported wecom media type or size for %q\", filename)\n\t}\n\n\ttotalChunks := (len(data) + wecomUploadChunkMaxBytes - 1) / wecomUploadChunkMaxBytes\n\tif totalChunks <= 0 || totalChunks > wecomUploadMaxChunks {\n\t\treturn nil, fmt.Errorf(\"wecom upload requires 1-%d chunks, got %d\", wecomUploadMaxChunks, totalChunks)\n\t}\n\n\tsum := md5.Sum(data)\n\tinitEnv, err := c.sendCommandAck(wecomCommand{\n\t\tCmd:     wecomCmdUploadMediaInit,\n\t\tHeaders: wecomHeaders{ReqID: randomID(10)},\n\t\tBody: wecomUploadMediaInitBody{\n\t\t\tType:        kind,\n\t\t\tFilename:    filename,\n\t\t\tTotalSize:   size,\n\t\t\tTotalChunks: totalChunks,\n\t\t\tMD5:         hex.EncodeToString(sum[:]),\n\t\t},\n\t}, wecomUploadTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tinitResp, err := decodeWeComEnvelopeBody[wecomUploadMediaInitResponse](initEnv)","sourceCodeStart":674,"sourceCodeEnd":710,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L674-L710","documentation":"Defensive invariant in the WeCom chunked uploader: the file must produce between 1 and 100 chunks of 512KB (wecomUploadChunkMaxBytes = 512<<10, wecomUploadMaxChunks = 100). In practice this branch is unreachable, because outboundWeComMediaKind() already rejected files over 20MB (<= 40 chunks) and files under 5 bytes (which would yield 0 chunks) before the chunk math runs. Seeing it means the upstream kind-classification guard was bypassed or the constants were changed.","triggerScenarios":"Only reachable if wecomOutboundMediaMaxBytes is raised above 100*512KB (~51.2MB) or the order of the kind check and chunk check is changed in uploadOutboundMedia; with current constants, len(data) is always 5..20,000,000, i.e. 1..40 chunks.","commonSituations":"A developer edits the upload limits in pkg/channels/wecom/media.go (e.g. raising the 20MB cap) without scaling wecomUploadMaxChunks; a fork adds a new media kind that skips the size guard; tests exercise the uploader with synthetic sizes past 50MB.","solutions":["If you modified the size constants, scale wecomUploadMaxChunks accordingly (chunks = ceil(size/512KB)) or split the payload into multiple messages","Otherwise treat this as a code-path bug: verify outboundWeComMediaKind ran before the chunk computation and file the invariant mismatch","Reduce the attachment below the 20MB outbound cap so the normal classification path applies"],"exampleFix":"// before (constant drift)\nwecomOutboundMediaMaxBytes = 60 << 20 // raised, but chunks still capped at 100 -> error 661\n\n// after\nwecomOutboundMediaMaxBytes = 60 << 20\nwecomUploadMaxChunks = 128 // 60MB / 512KB = 120 chunks, give headroom","handlingStrategy":"validation","validationCode":"// guard the invariant yourself if you tweak upload constants\nconst chunk = 512 << 10\nfunc chunkCountOK(size int64) bool {\n    n := (size + chunk - 1) / chunk\n    return n >= 1 && n <= 100\n}\n\nif !chunkCountOK(info.Size()) { /* split or reject before send */ }","typeGuard":"func isWecomChunkOverflow(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"wecom upload requires 1-\")\n}","tryCatchPattern":"if err := ch.Send(msg); err != nil {\n    if isWecomChunkOverflow(err) {\n        // configuration drift: re-check upload constants, then split the payload\n    }\n}","preventionTips":["Never raise size caps without recomputing wecomUploadMaxChunks (ceil(maxBytes/512KB))","Add a unit test asserting wecomUploadMaxChunks >= ceil(wecomOutboundMediaMaxBytes/chunk)","Split very large payloads into multiple messages at the application layer"],"tags":["wecom","media","upload","invariant","chunking"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}