gastownhall/beads · error

no cached clone for %s — run Ensure first

Error message

no cached clone for %s — run Ensure first

What it means

OpenStore() checks that a dolt database (.dolt directory) exists in the cache entry for the remote URL before opening it. This error means the cache was never populated — Ensure() has not successfully cloned (or the entry was evicted/deleted) — so there is no local clone to open a store from.

Source

Thrown at internal/remotecache/cache.go:173

	meta := c.readMeta(remoteURL)
	meta.LastPush = time.Now().UnixNano()
	c.writeMeta(remoteURL, meta)

	return nil
}

// OpenStore opens a DoltStorage from the cached clone using the provided
// StoreOpener. The cache entry directory is used as the beads directory.
// The caller is responsible for calling Close() on the returned store.
//
// Note: OpenStore does not acquire a cache lock. The caller must ensure
// no concurrent Ensure() or Push() is running against the same remoteURL,
// as those modify the underlying dolt database. This is safe for single-
// process CLI use but not for concurrent multi-process access.
func (c *Cache) OpenStore(ctx context.Context, remoteURL string, opener StoreOpener) (storage.DoltStorage, error) {
	entry := c.entryDir(remoteURL)
	if !c.doltExists(c.cloneTarget(remoteURL)) {
		return nil, fmt.Errorf("no cached clone for %s — run Ensure first", remoteURL)
	}
	return opener(ctx, entry)
}

// Evict removes a cached remote clone entirely.
func (c *Cache) Evict(remoteURL string) error {
	entry := c.entryDir(remoteURL)
	return os.RemoveAll(entry)
}

// doltExists checks if a dolt database exists at the given path.
func (c *Cache) doltExists(dbPath string) bool {
	doltDir := filepath.Join(dbPath, ".dolt")
	info, err := os.Stat(doltDir)
	return err == nil && info.IsDir()
}

// doltClone clones a remote into the target directory.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Call Cache.Ensure(ctx, remoteURL) before OpenStore — this clones or refreshes the cache entry.
  2. Check that Ensure() returned no error and that <entry>/<db>/.dolt exists on disk.
  3. If the entry should exist, verify you are passing the exact same remoteURL string (CacheKey is a hash of the URL; any difference maps to a different entry).
  4. Run `bd` commands normally (sync/pull) which handle Ensure internally before opening the store.

Example fix

// before: open directly, crashes with "no cached clone"
store, err := cache.OpenStore(ctx, remoteURL, opener)
// after: ensure first
if _, err := cache.Ensure(ctx, remoteURL); err != nil { return err }
store, err := cache.OpenStore(ctx, remoteURL, opener)
Defensive patterns

Strategy: validation

Validate before calling

entryDir := filepath.Join(cacheDir, "beads", "remotes", remotecache.CacheKey(remoteURL))
if _, err := os.Stat(filepath.Join(entryDir, "beads", ".dolt")); os.IsNotExist(err) {
    if _, err := cache.Ensure(ctx, remoteURL); err != nil {
        return fmt.Errorf("cache not populated: %w", err)
    }
}

Try / catch

store, err := cache.OpenStore(ctx, url, opener)
if err != nil && strings.Contains(err.Error(), "no cached clone") {
    if _, err := cache.Ensure(ctx, url); err != nil { return err }
    store, err = cache.OpenStore(ctx, url, opener)
}
if err != nil { return err }
defer store.Close()

Prevention

When it happens

Trigger: Calling Cache.OpenStore(ctx, remoteURL, opener) without a prior successful Cache.Ensure(ctx, remoteURL); after Cache.Evict() removed the entry; after a failed clone left no .dolt dir; or the user deleted the cache directory (~/.cache/beads/remotes/...).

Common situations: Fresh machine or CI runner where Ensure() was skipped; disk cleanup wiped the cache; calling OpenStore in a custom workflow that only ever calls Push(); clone previously failed silently.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/cc887d851f79a597. Report an issue: GitHub.