dutchcoders/transfer.sh · error

Could not save metadata

Error message

Could not save metadata

What it means

After successfully encoding the metadata JSON, postHandler calls s.storage.Put to persist it as `<filename>.metadata` alongside the file. If the storage backend (local filesystem, S3, GCS, etc.) returns an error, the server responds with 500 and this message. The file itself has not yet been uploaded, so the upload of that part failed entirely.

Source

Thrown at server/handlers.go:527

				if status != clamavScanStatusOK {
					s.logger.Printf("prescan positive: %s", status)
					http.Error(w, "Clamav prescan found a virus", http.StatusPreconditionFailed)
					return
				}
			}

			metadata := metadataForRequest(contentType, contentLength, s.randomTokenLength, r)

			buffer := &bytes.Buffer{}
			if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
				s.logger.Printf("%s", err.Error())
				http.Error(w, "Could not encode metadata", http.StatusInternalServerError)

				return
			} else if err := s.storage.Put(r.Context(), token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
				s.logger.Printf("%s", err.Error())
				http.Error(w, "Could not save metadata", http.StatusInternalServerError)

				return
			}

			s.logger.Printf("Uploading %s %s %d %s", token, filename, contentLength, contentType)

			reader, err := attachEncryptionReader(file, r.Header.Get("X-Encrypt-Password"))
			if err != nil {
				http.Error(w, "Could not crypt file", http.StatusInternalServerError)
				return
			}

			if err = s.storage.Put(r.Context(), token, filename, reader, contentType, uint64(contentLength)); err != nil {
				s.logger.Printf("Backend storage error: %s", err.Error())
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return

			}

View on GitHub (pinned to c37bfd9579)

Solutions

  1. Read the server log line emitted immediately before this response — it contains the underlying storage error.
  2. Verify storage backend configuration and credentials (bucket name, keys, service account).
  3. Check local storage path permissions and free disk space if using the local backend.
  4. Retry the upload; for transient cloud errors this typically succeeds later.
  5. As an operator, add health checks/monitoring for the storage backend.

Example fix

// before: retrying blindly without checking storage health
resp, _ := http.Post(url, mime, body)
// after: check backend reachability first
if err := checkStorageHealth(); err != nil {
    return fmt.Errorf("storage unavailable, postpone upload: %w", err)
}
resp, _ = http.Post(url, mime, body)
Defensive patterns

Strategy: retry

Validate before calling

// Health-check the storage backend before uploading
func storageHealthy() error {
    resp, err := http.Get(storageHealthURL)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode >= 500 {
        return fmt.Errorf("storage unhealthy: %s", resp.Status)
    }
    return nil
}

Try / catch

for attempt := 0; attempt < 3; attempt++ {
    resp, err := http.Post(url, mime, body)
    if err == nil && resp.StatusCode < 500 {
        break // not a transient storage failure
    }
    time.Sleep(backoff(attempt))
}

Prevention

When it happens

Trigger: Any storage.Put failure on the metadata object: backend outage, misconfigured bucket/container, expired or missing credentials, disk full on local storage, permission denied on the storage path, network timeout to the object store.

Common situations: S3 credentials revoked or bucket deleted; local temp/storage directory not writable by the server user; transient network errors to cloud storage; storage backend quota exceeded.

Related errors


AI-assisted analysis of dutchcoders/transfer.sh@c37bfd9579 (2026-09-05). Data as JSON: /api/errors/31c66e53aaa05226. Report an issue: GitHub.