dagger/dagger · error

decode persisted directory payload: unsupported form %q

Error message

decode persisted directory payload: unsupported form %q

What it means

Thrown when a persisted Directory payload has a Form value the decoder does not recognize (neither snapshot nor lazy). The decoder is a strict state machine over the persisted forms, so any unknown form — typically from a newer or older schema version — is rejected rather than misinterpreted. This protects against silently returning a wrong directory state.

Source

Thrown at core/directory.go:324

	case persistedDirectoryFormSnapshot:
		snapshot, err := loadPersistedImmutableSnapshotByResultID(ctx, dag, resultID, "directory", snapshotRole)
		if err != nil {
			return nil, err
		}
		dir.Snapshot.setValue(snapshot)
		return dir, nil
	case persistedDirectoryFormLazy:
		if persisted.LazyKind == "" {
			return nil, fmt.Errorf("decode persisted directory payload: missing lazy kind")
		}
		lazy, err := decodePersistedDirectoryLazy(ctx, dag, persisted.LazyKind, persisted.LazyJSON)
		if err != nil {
			return nil, err
		}
		dir.Lazy = lazy
		return dir, nil
	default:
		return nil, fmt.Errorf("decode persisted directory payload: unsupported form %q", persisted.Form)
	}
}

func (*Directory) DecodePersistedObject(ctx context.Context, dag *dagql.Server, resultID uint64, _ *dagql.ResultCall, payload json.RawMessage) (dagql.Typed, error) {
	return decodePersistedDirectoryWithSnapshotRole(ctx, dag, resultID, payload, "snapshot")
}

func loadCanonicalScratchDirectory(ctx context.Context) (string, bkcache.ImmutableRef, error) {
	query, err := CurrentQuery(ctx)
	if err != nil {
		return "", nil, fmt.Errorf("load canonical scratch directory: %w", err)
	}
	scratchSnapshot, err := query.SnapshotManager().Scratch(ctx)
	if err != nil {
		return "", nil, err
	}
	return "/", scratchSnapshot, nil
}

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Clear the engine cache and re-run so the directory is re-persisted with a form the current decoder understands
  2. Align engine and CLI versions (the payload schema is version-coupled)
  3. If writing payloads, only emit the registered form constants: "snapshot" or "lazy"

Example fix

// before
payload := persistedDirectoryPayload{Form: "directory_v2"}
// after
payload := persistedDirectoryPayload{Form: "lazy", LazyKind: kind, LazyJSON: blob}
Defensive patterns

Strategy: validation

Validate before calling

var p persistedDirectoryPayload
if err := json.Unmarshal(payload, &p); err != nil { return err }
if p.Form != "snapshot" && p.Form != "lazy" {
    return fmt.Errorf("unknown persisted directory form %q", p.Form)
}

Type guard

func isKnownDirectoryForm(f string) bool {
    return f == "snapshot" || f == "lazy"
}

Prevention

When it happens

Trigger: decodePersistedDirectoryWithSnapshotRole reads persisted.Form that is not persistedDirectoryFormSnapshot or persistedDirectoryFormLazy — e.g. empty form field, cache entry written by a different Dagger version with added forms, or corrupt/edited cache data.

Common situations: Engine/CLI version skew (payload written by a version with newer forms read by an older decoder); corrupted persisted-result cache; manual cache manipulation.

Related errors


AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05). Data as JSON: /api/errors/b478d897e1d643bd. Report an issue: GitHub.