sipeed/picoclaw · error

wecom upload finish returned empty media_id

Error message

wecom upload finish returned empty media_id

What it means

The WeCom chunked upload finished (all chunks sent, upload/finish acknowledged, response decoded) but the ack contained an empty media_id. Since media_id is required to reference the uploaded asset in the outbound message, the channel discards the result and errors out. Like error 662 this is a server/protocol anomaly, not bad input from the caller.

Source

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

		}
	}

	finishEnv, err := c.sendCommandAck(wecomCommand{
		Cmd:     wecomCmdUploadMediaEnd,
		Headers: wecomHeaders{ReqID: randomID(10)},
		Body: wecomUploadMediaFinishBody{
			UploadID: initResp.UploadID,
		},
	}, wecomUploadTimeout)
	if err != nil {
		return nil, err
	}
	finishResp, err := decodeWeComEnvelopeBody[wecomUploadMediaFinishResponse](finishEnv)
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(finishResp.MediaID) == "" {
		return nil, fmt.Errorf("wecom upload finish returned empty media_id")
	}

	uploaded := &wecomOutboundMedia{
		MsgType: kind,
		MediaID: finishResp.MediaID,
	}
	if kind == "video" {
		video := buildWeComVideoContent(finishResp.MediaID, filename, part.Caption)
		uploaded.Title = video.Title
		uploaded.Description = video.Description
	}
	return uploaded, nil
}

func fallbackWeComMediaText(part bus.MediaPart, kind, filename string) string {
	var lines []string
	if caption := strings.TrimSpace(part.Caption); caption != "" {
		lines = append(lines, caption)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Retry the media send once — the whole init/chunk/finish cycle restarts with a fresh upload_id
  2. Keep individual attachments well under the 20MB cap so the finish command is not delayed by long chunk transfers
  3. If it persists, dump the finish ack body and verify the wecomUploadMediaFinishResponse field mapping against the current WeCom protocol
  4. Rely on the built-in placeholder fallback for user-facing delivery while investigating
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

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

Try / catch

if err := ch.Send(msg); err != nil {
    if isWecomEmptyMediaID(err) {
        // full init/chunk/finish restarts on retry; usually succeeds
        time.Sleep(3 * time.Second)
        err = ch.Send(msg)
    }
}

Prevention

When it happens

Trigger: A full uploadOutboundMedia cycle where wecomCmdUploadMediaFinish returns a well-formed envelope whose MediaID field is empty or whitespace — e.g. the server expired the upload session between the last chunk and finish, or the finish ack schema changed.

Common situations: Long-running uploads that straddle a gateway restart or session expiry; transient server-side storage failure; WeCom protocol update renaming the field; note the caller-side code at media.go:752 already falls back to a placeholder message, so users typically see the fallback rather than a hard failure unless the error propagates.

Related errors


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