juicedata/juicefs · error

failed to marshal checkpoint: %w

Error message

failed to marshal checkpoint: %w

What it means

Save() serializes the current checkpoint (after collecting prefix states under locks) with json.Marshal. This error wraps a json.Marshal failure, meaning some field of the Checkpoint cannot be represented in JSON (e.g. unsupported type such as a channel/func, invalid number like NaN, or a custom MarshalJSON error). Save aborts and the checkpoint is not persisted, so progress since the last successful save would be lost on interruption.

Source

Thrown at pkg/sync/checkpoint.go:300

	for _, state := range ckpt.PrefixState {
		state.RLock()
	}
	m.multipartUploadStore.RLock()
	srcDelayDelMu.Lock()
	ckpt.SrcDelayDel = append([]string(nil), srcDelayDel...)
	srcDelayDelMu.Unlock()
	dstDelayDelMu.Lock()
	ckpt.DstDelayDel = append([]string(nil), dstDelayDel...)
	dstDelayDelMu.Unlock()
	data, err := json.Marshal(ckpt)
	m.multipartUploadStore.RUnlock()
	for _, state := range ckpt.PrefixState {
		state.RUnlock()
	}
	ckpt.Unlock()

	if err != nil {
		return fmt.Errorf("failed to marshal checkpoint: %w", err)
	}

	logger.Debugf("Saving checkpoint with %d prefixes, copied: %d, failed: %d",
		prefixCount, ckpt.Stats.Copied, ckpt.Stats.Failed)
	reader := bytes.NewReader(data)
	if err := m.dst.Put(ctx, m.checkpointKey, reader); err != nil {
		return fmt.Errorf("failed to put checkpoint: %w", err)
	}

	return nil
}

// ValidateConfig checks if checkpoint config matches current config
func (m *CheckpointManager) ValidateConfig(current *Config) bool {
	old := m.checkpoint.Config

	// Must match fields
	if old.Start != current.Start || old.End != current.End {

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Inspect the wrapped error to find the offending field; make it JSON-serializable (use plain types, fix NaN/Inf values)
  2. Check recent changes to the Checkpoint/multipartUploadState/PrefixState structs for non-serializable fields
  3. Add a marshal step test (TestCheckpointManagerSaveAndLoad covers round-trip) and fix the field type

Example fix

// before
Copied float64 // may hold NaN
// after: guard before saving
if math.IsNaN(ckpt.Stats.Copied) {
	ckpt.Stats.Copied = 0
}
data, err := json.Marshal(ckpt)
Defensive patterns

Strategy: try-catch

Try / catch

if err := mgr.Save(ctx); err != nil {
	if strings.Contains(err.Error(), "failed to marshal checkpoint") {
		logger.Errorf("checkpoint not persisted: %v", err) // inspect field named in the error
	}
}

Prevention

When it happens

Trigger: Periodic checkpoint save (or explicit Save from tests/seedCheckpoint) executes json.Marshal(ckpt) and it returns an error — typically due to a non-JSON-serializable value added to Checkpoint/PrefixState fields, e.g. NaN/Inf floats in Stats or an unexpected field type introduced in a code change.

Common situations: Developers extending Checkpoint with a new field of a non-marshalable type; counters corrupted to NaN; concurrent mutation causing inconsistent state passed to the encoder.

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 juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/e801b3f31c9a89de. Report an issue: GitHub.