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

  1. Use stock metadata.DeletionMark struct; remove any custom field that cannot be JSON-marshaled
  2. Inspect the wrapped error for the marshal failure detail and fix the offending field
  3. 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

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


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)