{"record":{"id":"490986f2297298a0","repo":"sipeed/picoclaw","slug":"matrix-upload-media-w","errorCode":null,"errorMessage":"matrix upload media: %w","messagePattern":"matrix upload media: %w","errorType":"exception","errorClass":"channels.ErrTemporary","httpStatus":null,"severity":"error","filePath":"pkg/channels/matrix/matrix.go","lineNumber":545,"sourceCode":"\t\t}\n\t\tif contentType == \"\" {\n\t\t\tcontentType = \"application/octet-stream\"\n\t\t}\n\n\t\tuploadResp, err := c.client.UploadMedia(sendCtx, mautrix.ReqUploadMedia{\n\t\t\tContent:       file,\n\t\t\tContentLength: fileInfo.Size(),\n\t\t\tContentType:   contentType,\n\t\t\tFileName:      filename,\n\t\t})\n\t\tfile.Close()\n\t\tif err != nil {\n\t\t\tlogger.ErrorCF(\"matrix\", \"Failed to upload media\", map[string]any{\n\t\t\t\t\"path\":  localPath,\n\t\t\t\t\"type\":  part.Type,\n\t\t\t\t\"error\": err.Error(),\n\t\t\t})\n\t\t\treturn nil, fmt.Errorf(\"matrix upload media: %w\", channels.ErrTemporary)\n\t\t}\n\n\t\tmsgType := matrixOutboundMsgType(part.Type, filename, contentType)\n\t\tcontent := matrixOutboundContent(\n\t\t\tpart.Caption,\n\t\t\tfilename,\n\t\t\tmsgType,\n\t\t\tcontentType,\n\t\t\tfileInfo.Size(),\n\t\t\tuploadResp.ContentURI.CUString(),\n\t\t)\n\n\t\tsendResp, err := c.client.SendMessageEvent(sendCtx, roomID, event.EventMessage, content)\n\t\tif err != nil {\n\t\t\tlogger.ErrorCF(\"matrix\", \"Failed to send media message\", map[string]any{\n\t\t\t\t\"room_id\": roomID.String(),\n\t\t\t\t\"type\":    msgType,\n\t\t\t\t\"error\":   err.Error(),","sourceCodeStart":527,"sourceCodeEnd":563,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/matrix/matrix.go#L527-L563","documentation":"Thrown by MatrixChannel.SendMedia when mautrix's UploadMedia (POST /_matrix/media/v3/upload) fails while uploading one resolved media part (pkg/channels/matrix/matrix.go:545). Classified as channels.ErrTemporary for manager retry with backoff; unlike the text-send path, the underlying error IS logged here (path, type, error) — read that log line for the real cause. Typical causes: file exceeding the homeserver's max_upload_size (413 M_TOO_LARGE), reverse-proxy body limits, expired auth, or network failure.","triggerScenarios":"Uploading a video/image larger than the homeserver limit (Synapse default ~50MB, often lower behind nginx client_max_body_size); slow uplink causing timeouts; access token revoked mid-session; homeserver media API disabled or rate-limited; Content-Length mismatch after the file changed on disk mid-send.","commonSituations":"Bots that generate large artifacts (rendered reports, screen recordings) hitting proxy limits; self-hosted Synapse behind a restrictive reverse proxy; media uploads on constrained networks (mobile uplinks, CI); files deleted or truncated between ResolveWithMeta and open.","solutions":["Check the logged error field for this exact upload (HTTP 413/M_TOO_LARGE vs timeout vs 401)","Query the homeserver limit (GET /_matrix/client/v1/media/config -> max_upload_size) and enforce it before sending: compress, downscale, or refuse oversized parts","Raise client_max_body_size / max_upload_size on your reverse proxy and homeserver if large media is required","For transient timeouts, rely on the manager's backoff retry; for 401, re-login"],"exampleFix":"// before: uploading whatever was generated\n err := ch.SendMedia(ctx, msg)\n\n// after: enforce the homeserver upload ceiling first\n const maxUpload = 50 << 20 // keep in sync with GET /_matrix/client/v1/media/config\n for _, p := range msg.Parts {\n \tif meta.Size > maxUpload {\n \t\treturn fmt.Errorf(\"part %s is %d bytes; homeserver limit %d: %w\", p.Ref, meta.Size, maxUpload, channels.ErrSendFailed)\n \t}\n }\n err := ch.SendMedia(ctx, msg)","handlingStrategy":"validation","validationCode":"// check homeserver upload ceiling before sending\nfunc withinUploadLimit(ctx context.Context, hs, token string, size int64) bool {\n\treq, _ := http.NewRequestWithContext(ctx, http.MethodGet, hs+\"/_matrix/client/v1/media/config\", nil)\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil { return true } // unknown limit: attempt anyway\n\tdefer resp.Body.Close()\n\tvar cfg struct{ MaxUploadSize int64 `json:\"m.upload.size\"` }\n\tif json.NewDecoder(resp.Body).Decode(&cfg) != nil { return true }\n\treturn cfg.MaxUploadSize <= 0 || size <= cfg.MaxUploadSize\n}","typeGuard":null,"tryCatchPattern":"if _, err := matrixCh.SendMedia(ctx, msg); err != nil {\n\tif errors.Is(err, channels.ErrTemporary) {\n\t\t// check the logged 'Failed to upload media' line: 413/M_TOO_LARGE will never succeed on retry\n\t\tif lastUploadErrWas413() { deadLetter(msg); return nil }\n\t\treturn err // network/timeout: manager backoff applies\n\t}\n\treturn err\n}","preventionTips":["Query max_upload_size at startup and enforce it before generating/uploading media","Set reverse-proxy client_max_body_size at or above the homeserver limit","Compress/downscale generated media at the source for chat delivery","Cache upload results so retries after a send failure don't re-upload bytes"],"tags":["go","matrix","media","upload","rate-limit","network","retry","temporary-failure"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}