sipeed/picoclaw · error

wecom upload init returned empty upload_id

Error message

wecom upload init returned empty upload_id

What it means

The WeCom upload protocol completed the upload/init handshake without error (the ack envelope decoded), but the response carried an empty or whitespace-only upload_id. Every subsequent chunk/finish command needs that upload_id, so the channel aborts immediately. This indicates a server-side or protocol-level anomaly rather than a client mistake.

Source

Thrown at pkg/channels/wecom/media.go:715

		Cmd:     wecomCmdUploadMediaInit,
		Headers: wecomHeaders{ReqID: randomID(10)},
		Body: wecomUploadMediaInitBody{
			Type:        kind,
			Filename:    filename,
			TotalSize:   size,
			TotalChunks: totalChunks,
			MD5:         hex.EncodeToString(sum[:]),
		},
	}, wecomUploadTimeout)
	if err != nil {
		return nil, err
	}
	initResp, err := decodeWeComEnvelopeBody[wecomUploadMediaInitResponse](initEnv)
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(initResp.UploadID) == "" {
		return nil, fmt.Errorf("wecom upload init returned empty upload_id")
	}

	for idx, offset := 0, 0; offset < len(data); idx, offset = idx+1, offset+wecomUploadChunkMaxBytes {
		end := offset + wecomUploadChunkMaxBytes
		if end > len(data) {
			end = len(data)
		}
		sendErr := c.sendCommand(wecomCommand{
			Cmd:     wecomCmdUploadMediaChunk,
			Headers: wecomHeaders{ReqID: randomID(10)},
			Body: wecomUploadMediaChunkBody{
				UploadID:   initResp.UploadID,
				ChunkIndex: idx,
				Base64Data: base64.StdEncoding.EncodeToString(data[offset:end]),
			},
		}, wecomUploadTimeout)
		if sendErr != nil {
			return nil, sendErr

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Retry the send after a short backoff — empty upload_id is usually transient
  2. Inspect the raw init ack (enable debug logging of the wecom channel) to confirm whether upload_id is truly absent vs. nested under a different key after a schema change
  3. If persistent, compare against the current WeCom upload protocol and update wecomUploadMediaInitResponse field tags
  4. Fall back to sending text-only content so the user gets a reply while media uploads are failing (the channel already does this for upload failures)
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

func isWecomEmptyUploadID(err error) bool {
    return err != nil && strings.Contains(err.Error(), "empty upload_id")
}

Try / catch

var lastErr error
for attempt := 0; attempt < 3; attempt++ {
    if err := ch.Send(msg); err != nil {
        if !isWecomEmptyUploadID(err) {
            lastErr = err
            break
        }
        time.Sleep(time.Duration(attempt+1) * 2 * time.Second)
        lastErr = err
        continue
    }
    lastErr = nil
    break
}

Prevention

When it happens

Trigger: Calling uploadOutboundMedia (i.e. sending any WeCom message with media) when the WeCom gateway acknowledges wecomCmdUploadMediaInit but omits upload_id in the decoded wecomUploadMediaInitResponse — server glitch, gateway version mismatch, or a truncated/malformed ack body that still passed envelope decoding.

Common situations: Transient WeCom service disruption during a burst of uploads; the WeCom WebSocket gateway was upgraded and changed the init ack schema; a proxy/middlebox rewrote or truncated the response body; extremely rare in steady state.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/50a91d45be6f2d18. Report an issue: GitHub.