{"record":{"id":"ca61450cffd9abf9","repo":"syncthing/syncthing","slug":"upload-s","errorCode":null,"errorMessage":"upload: %s","messagePattern":"upload: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/syncthing/crash_reporting.go","lineNumber":116,"sourceCode":"\t}\n\n\tputReq, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Set a reasonable timeout on the PUT request\n\tputCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout)\n\tdefer putCancel()\n\tputReq = putReq.WithContext(putCtx)\n\n\tresp, err = http.DefaultClient.Do(putReq)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"upload: %s\", resp.Status)\n\t}\n\n\treturn nil\n}\n\n// filterLogLines returns the data without any log lines between the first\n// line and the panic trace. This is done in-place: the original data slice\n// is destroyed.\nfunc filterLogLines(data []byte) []byte {\n\tfiltered := data[:0]\n\tmatched := false\n\tfor line := range bytes.SplitSeq(data, []byte(\"\\n\")) {\n\t\tswitch {\n\t\tcase !matched && bytes.HasPrefix(line, []byte(\"Panic \")):\n\t\t\t// This begins the panic trace, set the matched flag and append.\n\t\t\tmatched = true\n\t\t\tfallthrough\n\t\tcase len(filtered) == 0 || matched:","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/syncthing/syncthing/blob/058bcd7334839663cf569501d3ac539034d45cb5/cmd/syncthing/crash_reporting.go#L98-L134","documentation":"Returned by uploadPanicLog (cmd/syncthing/crash_reporting.go) when the PUT of a panic log to the crash reporting server completed at the HTTP level but returned any status other than 200 OK. The HEAD dedup check (200 = already reported) passed, so this branch means the server actively rejected or failed the upload (e.g. 4xx/5xx, gateway error).","triggerScenarios":"Crash reporting enabled (STCRASHURL / crash URL configured) and: the crash server returns 5xx under load, a proxy returns 403/502, the URL base is misconfigured so the PUT path is invalid (404), or the payload is rejected (413). Timeouts are NOT this error (they surface as client.Do errors); only a completed non-OK response triggers it.","commonSituations":"Self-hosted or proxied crash-reporting endpoints; corporate proxies intercepting the PUT; transient outage of the vendor crash server; typo'd crash-reporting base URL that still serves HTTP.","solutions":["Treat as non-fatal: the panic log stays on disk (not renamed to .reported.log) and is retried on next crash-reporting run — inspect the panic log locally instead","Check network egress: proxies, TLS interception, or firewall rules rewriting/blocking PUT requests","If the crash URL is custom-configured, verify the endpoint accepts HEAD+PUT at /<sha256> paths","Retry later if the crash server was having a transient outage (the log is never lost)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isUploadStatusErr(err error) bool {\n\treturn err != nil && strings.HasPrefix(err.Error(), \"upload: \")\n}","tryCatchPattern":"// Crash reporting is best-effort by design; never let it fail the parent flow\nif err := uploadPanicLog(ctx, urlBase, file); err != nil {\n\tif isUploadStatusErr(err) {\n\t\tslog.WarnContext(ctx, \"crash server rejected upload, will retry next run\", slogutil.Error(err))\n\t} else {\n\t\tslog.ErrorContext(ctx, \"Reporting crash\", slogutil.Error(err))\n\t}\n}","preventionTips":["Treat crash-report upload failures as non-fatal noise; the panic log persists locally until successfully reported","Verify custom crash URLs accept HEAD and PUT at /<sha256-of-log>","Keep egress proxies from rewriting PUT bodies or blocking non-GET methods"],"tags":["network","crash-reporting","http","upload"],"backgroundTag":null,"analyzedSha":"058bcd7334839663cf569501d3ac539034d45cb5","analyzedAt":"2026-08-15T07:53:43.174Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}