thanos-io/thanos · error
json encode deletion mark
Error message
json encode deletion mark
What it means
MarkForDeletion serializes a deletion-mark.json object (block ID, timestamp, details) to JSON before uploading it to the bucket. This error wraps a failure of json.Marshal on that structure, which practically never fails unless the encoder is misused.
Solutions
- Use stock metadata.DeletionMark struct; remove any custom field that cannot be JSON-marshaled
- Inspect the wrapped error for the marshal failure detail and fix the offending field
- Upgrade to an unmodified Thanos release
Example fix
// before
type DeletionMark struct { Done chan struct{} `json:"done"` }
// after
type DeletionMark struct { ID ulid.ULID; DeletionTime int64; Version int } Defensive patterns
Strategy: try-catch
Try / catch
if err := block.MarkForDeletion(ctx, logger, bkt, id, details); err != nil {
if strings.Contains(err.Error(), "json encode deletion mark") {
return fmt.Errorf("deletion mark serialization failed (check custom metadata structs): %w", err)
}
return err
} Prevention
- Do not add unmarshalable fields to metadata.DeletionMark
- Test MarkForDeletion in CI with the exact binary you deploy
When it happens
Trigger: json.Marshal returning an error while encoding metadata.DeletionMark in MarkForDeletion — essentially only possible via unsupported types or custom marshaling bugs in the DeletionMark struct.
Common situations: Custom forks that added unmarshalable fields (e.g. channels, funcs) to DeletionMark; corrupted vendored/patched versions of the metadata package.
Understand the failure class
Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.
Related errors
- read meta
- received message larger than max
- error marshaling proto response
- error marshaling response
- could not unmarshal parameter
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/5932e1c124c0f7e7.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/block.go:186
func MarkForDeletion(ctx context.Context, logger log.Logger, bkt objstore.Bucket, id ulid.ULID, details string, markedForDeletion prometheus.Counter) error {
deletionMarkFile := path.Join(id.String(), metadata.DeletionMarkFilename)
deletionMarkExists, err := bkt.Exists(ctx, deletionMarkFile)
if err != nil {
return errors.Wrapf(err, "check exists %s in bucket", deletionMarkFile)
}
if deletionMarkExists {
level.Warn(logger).Log("msg", "requested to mark for deletion, but file already exists; this should not happen; investigate", "err", errors.Errorf("file %s already exists in bucket", deletionMarkFile))
return nil
}
deletionMark, err := json.Marshal(metadata.DeletionMark{
ID: id,
DeletionTime: time.Now().Unix(),
Version: metadata.DeletionMarkVersion1,
Details: details,
})
if err != nil {
return errors.Wrap(err, "json encode deletion mark")
}
if err := bkt.Upload(ctx, deletionMarkFile, bytes.NewBuffer(deletionMark)); err != nil {
return errors.Wrapf(err, "upload file %s to bucket", deletionMarkFile)
}
markedForDeletion.Inc()
level.Info(logger).Log("msg", "block has been marked for deletion", "block", id)
return nil
}
// Delete removes directory that is meant to be block directory.
// NOTE: Always prefer this method for deleting blocks.
// - We have to delete block's files in the certain order (meta.json first and deletion-mark.json last)
// to ensure we don't end up with malformed partial blocks. Thanos system handles well partial blocks
// only if they don't have meta.json. If meta.json is present Thanos assumes valid block.
// - This avoids deleting empty dir (whole bucket) by mistake.
func Delete(ctx context.Context, logger log.Logger, bkt objstore.Bucket, id ulid.ULID) error {
metaFile := path.Join(id.String(), MetaFilename)View on GitHub (pinned to 35b8b99117)