dutchcoders/transfer.sh · error

could not save metadata

Error message

could not save metadata

What it means

When incrementing the download counter, checkMetadata writes the updated metadata back to storage as <filename>.metadata via s.storage.Put. If the storage backend returns an error, the download is aborted with this error. It means the server could not persist the incremented download count even though the file itself exists.

Source

Thrown at server/handlers.go:899

	}

	if err := json.NewDecoder(r).Decode(&metadata); err != nil {
		return metadata, err
	} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {
		return metadata, errors.New("maxDownloads expired")
	} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {
		return metadata, errors.New("maxDate expired")
	} else if metadata.MaxDownloads != -1 && increaseDownload {
		// todo(nl5887): mutex?

		// update number of downloads
		metadata.Downloads++

		buffer := &bytes.Buffer{}
		if err := json.NewEncoder(buffer).Encode(metadata); err != nil {
			return metadata, errors.New("could not encode metadata")
		} else if err := s.storage.Put(ctx, token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
			return metadata, errors.New("could not save metadata")
		}
	}

	return metadata, nil
}

func (s *Server) checkDeletionToken(ctx context.Context, deletionToken, token, filename string) error {
	s.lock(token, filename)
	defer s.unlock(token, filename)

	var metadata metadata

	r, _, err := s.storage.Get(ctx, token, fmt.Sprintf("%s.metadata", filename), nil)
	defer storage.CloseCheck(r)

	if s.storage.IsNotExist(err) {
		return errors.New("metadata doesn't exist")
	} else if err != nil {

View on GitHub (pinned to c37bfd9579)

Solutions

  1. Check the storage backend health and credentials (bucket access, tokens, disk space/permissions on basedir).
  2. Inspect server logs for the underlying storage error returned by Put to identify the backend-specific cause.
  3. Retry the download once the backend is reachable; the count increment failure is transient in outage cases.
  4. For local storage, verify the basedir is writable by the server process user; for S3 verify IAM permissions include PutObject.

Example fix

// before
} else if err := s.storage.Put(ctx, token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
	return metadata, errors.New("could not save metadata")
}
// after
} else if err := s.storage.Put(ctx, token, fmt.Sprintf("%s.metadata", filename), buffer, "text/json", uint64(buffer.Len())); err != nil {
	return metadata, fmt.Errorf("could not save metadata: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

# check backend reachability before bulk downloads
curl -sf http://storage-backend-health || echo "storage down"

Try / catch

if err != nil && strings.Contains(err.Error(), "could not save metadata") {
	time.Sleep(backoff)
	return retryDownload() // transient backend outage
}

Prevention

When it happens

Trigger: GET/HEAD/preview/archive download of a file with a finite MaxDownloads where the storage Put call for the .metadata object fails — e.g. S3/GDrive/Storj/local backend outage, permission denied on the local basedir, bucket credentials revoked, or network failure to the backend.

Common situations: Local basedir made read-only or disk full; S3 bucket policy/credentials changed after uploads; Google Drive API quota or token expiry; storage backend temporarily unreachable mid-download.

Related errors


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