{"record":{"id":"32adadf19740e40b","repo":"chenhg5/cc-connect","slug":"upload-init-empty-upload-id","errorCode":null,"errorMessage":"upload init: empty upload_id","messagePattern":"upload init: empty upload_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wecom/websocket_outbound_media.go","lineNumber":77,"sourceCode":"\t\t\t\"type\":         mediaType,\n\t\t\t\"filename\":     filename,\n\t\t\t\"total_size\":   len(data),\n\t\t\t\"total_chunks\": totalChunks,\n\t\t\t\"md5\":          hex.EncodeToString(sum[:]),\n\t\t},\n\t}\n\tinitResp, err := p.writeAndWaitFrameWithTimeout(ctx, initFrame, initReqID, wsMediaAckTimeout)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"upload init: %w\", err)\n\t}\n\tvar initBody struct {\n\t\tUploadID string `json:\"upload_id\"`\n\t}\n\tif err := json.Unmarshal(initResp.Body, &initBody); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode upload init response: %w\", err)\n\t}\n\tif initBody.UploadID == \"\" {\n\t\treturn \"\", fmt.Errorf(\"upload init: empty upload_id\")\n\t}\n\n\tfor i := 0; i < totalChunks; i++ {\n\t\tstart := i * wecomWSUploadChunkSize\n\t\tend := start + wecomWSUploadChunkSize\n\t\tif end > len(data) {\n\t\t\tend = len(data)\n\t\t}\n\t\treqID := p.generateReqID(\"aibot_upload_media_chunk\")\n\t\tchunkFrame := map[string]any{\n\t\t\t\"cmd\":     \"aibot_upload_media_chunk\",\n\t\t\t\"headers\": map[string]string{\"req_id\": reqID},\n\t\t\t\"body\": map[string]any{\n\t\t\t\t\"upload_id\":   initBody.UploadID,\n\t\t\t\t\"chunk_index\": i,\n\t\t\t\t\"base64_data\": base64.StdEncoding.EncodeToString(data[start:end]),\n\t\t\t},\n\t\t}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wecom/websocket_outbound_media.go#L59-L95","documentation":"The aibot_upload_media_init ack decoded successfully as JSON, but the upload_id field is empty. The upload_id is required to reference the subsequent chunk uploads, so without it the chunked upload cannot proceed and uploadWSMedia aborts with this explicit error rather than sending chunks the server can't reassemble.","triggerScenarios":"SendImage/SendFile when the WeCom server acks aibot_upload_media_init with a body lacking upload_id (e.g. an error ack like {\"errcode\":...} that still parses as JSON, or a schema where the field was renamed) so initBody.UploadID ends up \"\".","commonSituations":"WeCom API version change renaming or nesting the upload_id field; server-side rejection (auth expired, quota, invalid media type) returned in a JSON body without upload_id; bot credentials lacking the upload permission; sending a media_type the server session doesn't accept.","solutions":["Log the full ack body to check for an embedded error code/message the server returned instead of an upload_id.","Verify bot credentials/token are valid and not expired — rejections often arrive as JSON without upload_id.","Check for a WeCom API schema change and update the initBody struct (e.g. nested field or renamed key).","Confirm the mediaType passed by SendImage/SendFile is one the server accepts for this upload command."],"exampleFix":"// before\nif initBody.UploadID == \"\" {\n    return \"\", fmt.Errorf(\"upload init: empty upload_id\")\n}\n\n// after\nif initBody.UploadID == \"\" {\n    if initBody.ErrCode != 0 {\n        return \"\", fmt.Errorf(\"upload init rejected: errcode=%d errmsg=%s\", initBody.ErrCode, initBody.ErrMsg)\n    }\n    return \"\", fmt.Errorf(\"upload init: empty upload_id (body=%q)\", string(initResp.Body))\n}","handlingStrategy":"type-guard","validationCode":"type initAck struct {\n    UploadID string `json:\"upload_id\"`\n    ErrCode  int    `json:\"errcode\"`\n    ErrMsg   string `json:\"errmsg\"`\n}\nfunc ackHasUploadID(body []byte) bool {\n    var a initAck\n    if json.Unmarshal(body, &a) != nil {\n        return false\n    }\n    return a.ErrCode == 0 && a.UploadID != \"\"\n}","typeGuard":"func uploadIDFromAck(body []byte) (string, error) {\n    var a initAck\n    if err := json.Unmarshal(body, &a); err != nil {\n        return \"\", err\n    }\n    if a.ErrCode != 0 {\n        return \"\", fmt.Errorf(\"server error %d: %s\", a.ErrCode, a.ErrMsg)\n    }\n    if a.UploadID == \"\" {\n        return \"\", fmt.Errorf(\"ack missing upload_id: %s\", string(body))\n    }\n    return a.UploadID, nil\n}","tryCatchPattern":"uploadID, err := uploadIDFromAck(initResp.Body)\nif err != nil {\n    slog.Warn(\"wecom upload init: no upload_id\", \"body\", string(initResp.Body), \"err\", err)\n    return \"\", fmt.Errorf(\"upload init: %w\", err)\n}","preventionTips":["Treat a JSON ack without upload_id as a server error response, not just an empty field — surface errcode/errmsg.","Refresh bot credentials before uploads; expired tokens commonly produce id-less acks.","Keep a regression test with a captured rejected-ack fixture to detect WeCom schema changes.","Verify the mediaType/name are acceptable to the server before initiating the upload."],"tags":["wecom","websocket","media-upload","api-response","empty-field"],"backgroundTag":"empty-api-response","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"}