{"record":{"id":"92c80d5d85fb881a","repo":"dutchcoders/transfer.sh","slug":"could-not-save-metadata","errorCode":null,"errorMessage":"could not save metadata","messagePattern":"could not save metadata","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"server/handlers.go","lineNumber":899,"sourceCode":"\t}\n\n\tif err := json.NewDecoder(r).Decode(&metadata); err != nil {\n\t\treturn metadata, err\n\t} else if metadata.MaxDownloads != -1 && metadata.Downloads >= metadata.MaxDownloads {\n\t\treturn metadata, errors.New(\"maxDownloads expired\")\n\t} else if !metadata.MaxDate.IsZero() && time.Now().After(metadata.MaxDate) {\n\t\treturn metadata, errors.New(\"maxDate expired\")\n\t} else if metadata.MaxDownloads != -1 && increaseDownload {\n\t\t// todo(nl5887): mutex?\n\n\t\t// update number of downloads\n\t\tmetadata.Downloads++\n\n\t\tbuffer := &bytes.Buffer{}\n\t\tif err := json.NewEncoder(buffer).Encode(metadata); err != nil {\n\t\t\treturn metadata, errors.New(\"could not encode metadata\")\n\t\t} else if err := s.storage.Put(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), buffer, \"text/json\", uint64(buffer.Len())); err != nil {\n\t\t\treturn metadata, errors.New(\"could not save metadata\")\n\t\t}\n\t}\n\n\treturn metadata, nil\n}\n\nfunc (s *Server) checkDeletionToken(ctx context.Context, deletionToken, token, filename string) error {\n\ts.lock(token, filename)\n\tdefer s.unlock(token, filename)\n\n\tvar metadata metadata\n\n\tr, _, err := s.storage.Get(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), nil)\n\tdefer storage.CloseCheck(r)\n\n\tif s.storage.IsNotExist(err) {\n\t\treturn errors.New(\"metadata doesn't exist\")\n\t} else if err != nil {","sourceCodeStart":881,"sourceCodeEnd":917,"githubUrl":"https://github.com/dutchcoders/transfer.sh/blob/c37bfd95797fd6da8a6da53fc13d191994b3f687/server/handlers.go#L881-L917","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the storage backend health and credentials (bucket access, tokens, disk space/permissions on basedir).","Inspect server logs for the underlying storage error returned by Put to identify the backend-specific cause.","Retry the download once the backend is reachable; the count increment failure is transient in outage cases.","For local storage, verify the basedir is writable by the server process user; for S3 verify IAM permissions include PutObject."],"exampleFix":"// before\n} else if err := s.storage.Put(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), buffer, \"text/json\", uint64(buffer.Len())); err != nil {\n\treturn metadata, errors.New(\"could not save metadata\")\n}\n// after\n} else if err := s.storage.Put(ctx, token, fmt.Sprintf(\"%s.metadata\", filename), buffer, \"text/json\", uint64(buffer.Len())); err != nil {\n\treturn metadata, fmt.Errorf(\"could not save metadata: %w\", err)\n}","handlingStrategy":"retry","validationCode":"# check backend reachability before bulk downloads\ncurl -sf http://storage-backend-health || echo \"storage down\"","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"could not save metadata\") {\n\ttime.Sleep(backoff)\n\treturn retryDownload() // transient backend outage\n}","preventionTips":["Monitor storage backend health and credentials in production.","Ensure the local basedir is writable and has free disk space.","For S3, verify IAM policy allows PutObject on the metadata objects."],"tags":["storage","persistence","metadata","back-end"],"backgroundTag":"storage-write-failed","analyzedSha":"c37bfd95797fd6da8a6da53fc13d191994b3f687","analyzedAt":"2026-09-05T10:21:07.548Z","contentChangedAt":"2026-09-05T10:21:07.548Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}