thanos-io/thanos · error
write meta file
Error message
write meta file
What it means
This error wraps a failure from metadata.Meta.WriteToDir, which serializes the block meta (with Thanos extensions: labels, source, segment files) as meta.json into the block directory before upload. It is thrown when writing/flushing meta.json to the block dir fails, typically due to filesystem permission, disk-full, or directory-state issues.
Solutions
- Check free disk space on the data volume (df -h) and free space or grow the volume.
- Fix permissions/ownership of the blocks directory so the shipping process can write meta.json.
- Ensure the volume is mounted read-write (check /proc/mounts for 'ro').
- Verify the block directory still exists (no concurrent cleanup); re-run Sync after the block is restored.
Example fix
// before: container runs as uid 65534 against root-owned data $ kubectl exec sidecar -- ls -ld /prometheus // after: set matching securityContext securityContext: runAsUser: 0 # or chown data dir to the sidecar uid fsGroup: 2000
Defensive patterns
Strategy: validation
Validate before calling
info, err := os.Stat(absUpdir)
if err != nil {
return fmt.Errorf("block dir missing: %w", err)
}
if !info.IsDir() {
return errors.New("block path is not a directory")
}
// probe writability
probe := filepath.Join(absUpdir, ".write-test")
if err := os.WriteFile(probe, nil, 0o600); err != nil {
return fmt.Errorf("block dir not writable: %w", err)
}
os.Remove(probe) Type guard
func dirWritable(path string) bool {
probe := filepath.Join(path, ".writetest")
if err := os.WriteFile(probe, nil, 0o600); err != nil {
return false
}
os.Remove(probe)
return true
} Try / catch
if err := meta.WriteToDir(s.logger, absUpdir); err != nil {
if errors.Is(err, syscall.ENOSPC) {
return errors.Wrap(err, "write meta file: disk full, free space and retry")
}
if os.IsPermission(err) {
return errors.Wrap(err, "write meta file: check volume permissions/ownership")
}
return errors.Wrap(err, "write meta file")
} Prevention
- Alert on disk usage of the Prometheus data volume before it fills.
- Run the shipping process with the same uid/gid that owns the TSDB dir (or set fsGroup in Kubernetes).
- Ensure volumes are mounted read-write; ro mounts cause immediate write failures.
- Avoid concurrent cleanup jobs that could delete block dirs mid-write.
When it happens
Trigger: Calling Shipper.Sync when the block directory (hardlinked ULID dir) is not writable by the process, the filesystem is out of space or read-only, or the directory disappeared between hardlink and meta write (concurrent deletion).
Common situations: Read-only volume after pod rescheduling; disk-full on the Prometheus data volume; running the process as non-root against a data dir owned by another user; antivirus/backup tooling locking files on the volume.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/cd2d08f84987bf86.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/shipper/shipper.go:512
level.Error(s.logger).Log("msg", "failed to clean upload directory", "err", err)
}
}()
absUpdir := filepath.Join(s.dir.Name(), updir)
dir := filepath.Join(s.dir.Name(), meta.ULID.String())
if err := hardlinkBlock(dir, absUpdir); err != nil {
return errors.Wrap(err, "hard link block")
}
// Attach current labels and write a new meta file with Thanos extensions.
if lset := s.labels(); !lset.IsEmpty() {
lset.Range(func(l labels.Label) {
meta.Thanos.Labels[l.Name] = l.Value
})
}
meta.Thanos.Source = s.source
meta.Thanos.SegmentFiles = block.GetSegmentFiles(absUpdir)
if err := meta.WriteToDir(s.logger, absUpdir); err != nil {
return errors.Wrap(err, "write meta file")
}
var uploadOptions []objstore.UploadOption
if s.uploadConcurrency > 0 {
uploadOptions = append(uploadOptions, objstore.WithUploadConcurrency(s.uploadConcurrency))
}
return block.Upload(ctx, s.logger, s.bucket, absUpdir, s.hashFunc, uploadOptions...)
}
// blockMetasFromOldest returns the block meta of each block found in dir
// sorted by minTime asc.
func (s *Shipper) blockMetasFromOldest() (metas []*metadata.Meta, failedBlocks []string, _ error) {
dir, err := s.dir.Open(".")
if err != nil {
return nil, nil, errors.Wrap(err, "open dir")
}
defer runutil.CloseWithLogOnErr(s.logger, dir, "shipper dir")
View on GitHub (pinned to 35b8b99117)