benbjohnson/litestream · error

failed to format response: %w

Error message

failed to format response: %w

What it means

When --dry-run succeeds, restore can emit the plan as JSON via json.MarshalIndent. If marshaling the plan structure fails (unexpectedly, since the plan is composed of marshalable types), the error is wrapped as "failed to format response". This is a defensive serialization guard on the CLI output path, not a restore failure — the plan itself was computed successfully.

Source

Thrown at cmd/litestream/restore.go:128

		if r, err = c.loadFromConfig(ctx, fs.Arg(0), *configPath, !*noExpandEnv, *ifDBNotExists, &opt); errors.Is(err, errSkipDBExists) {
			slog.Info("database already exists, skipping")
			return nil
		} else if err != nil {
			return err
		}
	}

	if *dryRun {
		plan, err := c.dryRunPlan(ctx, fs.Arg(0), r, opt)
		if errors.Is(err, litestream.ErrTxNotAvailable) {
			return fmt.Errorf("no matching backup files available")
		} else if err != nil {
			return err
		}
		if *jsonOutput {
			output, err := json.MarshalIndent(plan, "", "  ")
			if err != nil {
				return fmt.Errorf("failed to format response: %w", err)
			}
			fmt.Println(string(output))
			return nil
		}
		c.printDryRunPlan(plan)
		return nil
	}

	if !opt.Follow {
		if err := c.prepareOutputPath(opt.OutputPath, *force); err != nil {
			return err
		}
	}

	txid := c.restoreTXID(ctx, r, opt)
	start := time.Now()
	if err := r.Restore(ctx, opt); errors.Is(err, litestream.ErrTxNotAvailable) {
		if *ifReplicaExists {

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Re-run with the non-JSON dry-run output (omit --json) to still see the plan via c.printDryRunPlan
  2. Check the wrapped inner error — it identifies the offending type/value
  3. If running a patched build, ensure all fields added to the plan struct are JSON-serializable
  4. Report upstream if it reproduces on a vanilla build

Example fix

// before (fork added non-serializable field)
type plan struct { Meta meta; Ch chan int `json:"-"` }
// after
type plan struct { Meta meta }
Defensive patterns

Strategy: try-catch

Try / catch

output, err := json.MarshalIndent(plan, "", "  ")
if err != nil {
    // fall back to human-readable output instead of failing
    printDryRunPlan(plan)
    return nil
}

Prevention

When it happens

Trigger: `litestream restore --dry-run --json ...` where json.MarshalIndent(plan) returns an error — in practice only from an unsupported value type slipping into the plan struct (e.g. channel/func field) or a custom MarshalJSON returning an error.

Common situations: Very rare; typically encountered after local modifications/forks of the CLI that add non-serializable fields to the dry-run plan, or custom JSON marshaling bugs.

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 benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/af9fb574945b87f2. Report an issue: GitHub.