syncthing/syncthing · warning

upload: %s

Error message

upload: %s

What it means

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).

Source

Thrown at cmd/syncthing/crash_reporting.go:116

	}

	putReq, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
	if err != nil {
		return err
	}

	// Set a reasonable timeout on the PUT request
	putCtx, putCancel := context.WithTimeout(ctx, putRequestTimeout)
	defer putCancel()
	putReq = putReq.WithContext(putCtx)

	resp, err = http.DefaultClient.Do(putReq)
	if err != nil {
		return err
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("upload: %s", resp.Status)
	}

	return nil
}

// filterLogLines returns the data without any log lines between the first
// line and the panic trace. This is done in-place: the original data slice
// is destroyed.
func filterLogLines(data []byte) []byte {
	filtered := data[:0]
	matched := false
	for line := range bytes.SplitSeq(data, []byte("\n")) {
		switch {
		case !matched && bytes.HasPrefix(line, []byte("Panic ")):
			// This begins the panic trace, set the matched flag and append.
			matched = true
			fallthrough
		case len(filtered) == 0 || matched:

View on GitHub (pinned to 058bcd7334)

Solutions

  1. 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
  2. Check network egress: proxies, TLS interception, or firewall rules rewriting/blocking PUT requests
  3. If the crash URL is custom-configured, verify the endpoint accepts HEAD+PUT at /<sha256> paths
  4. Retry later if the crash server was having a transient outage (the log is never lost)
Defensive patterns

Strategy: try-catch

Type guard

func isUploadStatusErr(err error) bool {
	return err != nil && strings.HasPrefix(err.Error(), "upload: ")
}

Try / catch

// Crash reporting is best-effort by design; never let it fail the parent flow
if err := uploadPanicLog(ctx, urlBase, file); err != nil {
	if isUploadStatusErr(err) {
		slog.WarnContext(ctx, "crash server rejected upload, will retry next run", slogutil.Error(err))
	} else {
		slog.ErrorContext(ctx, "Reporting crash", slogutil.Error(err))
	}
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of syncthing/syncthing@058bcd7334 (2026-08-15). Data as JSON: /api/errors/ca61450cffd9abf9. Report an issue: GitHub.