dagger/dagger · error

missing persisted %s snapshot link role %q for result %d

Error message

missing persisted %s snapshot link role %q for result %d

What it means

The cache returned snapshot links for the result ID, but none matched the requested role string. The persisted record exists but lacks a link for the specific role (e.g. input vs output snapshot) the decoder needs, indicating an incomplete or differently-versioned persisted record.

Source

Thrown at core/persisted_object.go:136

		return dagql.PersistedSnapshotRefLink{}, fmt.Errorf("load persisted %s snapshot link: zero result ID", label)
	}
	if _, err := persistedDecodeQuery(dag); err != nil {
		return dagql.PersistedSnapshotRefLink{}, fmt.Errorf("load persisted %s snapshot link query: %w", label, err)
	}
	cache, err := dagql.EngineCache(ctx)
	if err != nil {
		return dagql.PersistedSnapshotRefLink{}, fmt.Errorf("load persisted %s snapshot link cache: %w", label, err)
	}
	links, err := cache.PersistedSnapshotLinksByResultID(ctx, resultID)
	if err != nil {
		return dagql.PersistedSnapshotRefLink{}, fmt.Errorf("load persisted %s snapshot link: %w", label, err)
	}
	for _, link := range links {
		if link.Role == role {
			return link, nil
		}
	}
	return dagql.PersistedSnapshotRefLink{}, fmt.Errorf("missing persisted %s snapshot link role %q for result %d", label, role, resultID)
}

func loadPersistedSnapshotLinksByResultID(ctx context.Context, dag *dagql.Server, resultID uint64, label string) ([]dagql.PersistedSnapshotRefLink, error) {
	if resultID == 0 {
		return nil, fmt.Errorf("load persisted %s snapshot links: zero result ID", label)
	}
	if _, err := persistedDecodeQuery(dag); err != nil {
		return nil, fmt.Errorf("load persisted %s snapshot links query: %w", label, err)
	}
	cache, err := dagql.EngineCache(ctx)
	if err != nil {
		return nil, fmt.Errorf("load persisted %s snapshot links cache: %w", label, err)
	}
	links, err := cache.PersistedSnapshotLinksByResultID(ctx, resultID)
	if err != nil {
		return nil, fmt.Errorf("load persisted %s snapshot links: %w", label, err)
	}
	return links, nil

View on GitHub (pinned to 82ba2681db)

Solutions

  1. Verify the persisted payload was written by a version that records the requested role; re-persist if not.
  2. Check which roles exist for the result ID and use the correct role string.
  3. Handle role absence gracefully for legacy payloads (skip snapshot or backfill).
  4. Confirm the role constant used at persist time matches the one at decode time.

Example fix

// before
link, err := loadPersistedSnapshotLinkByResultID(ctx, dag, id, "container", roleOutputSnapshot)
// after
links, err := loadPersistedSnapshotLinksByResultID(ctx, dag, id, "container")
if len(links) == 0 { /* legacy payload: proceed without snapshot */ }
Defensive patterns

Strategy: fallback

Validate before calling

links, err := cache.PersistedSnapshotLinksByResultID(ctx, resultID)
if err == nil && !hasRole(links, role) {
	// use legacy path or backfill before decode
}

Type guard

func hasRole(links []dagql.PersistedSnapshotRefLink, role string) bool {
	for _, l := range links {
		if l.Role == role { return true }
	}
	return false
}

Try / catch

err := decodePersistedObject(ctx, dag, id)
if err != nil && strings.Contains(err.Error(), "snapshot link role") {
	// decode without snapshot or migrate the payload
}

Prevention

When it happens

Trigger: loadPersistedSnapshotLinkByResultID iterating links and finding no link.Role == role when called from DecodePersistedObject or loadPersistedImmutableSnapshotByResultID — e.g. a payload persisted by an older version that didn't record the requested role.

Common situations: Version drift between the code that persisted the payload and the code decoding it (role names or roles recorded changed); manual payload construction omitting a role; role string typo in custom tooling.

Related errors


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