gastownhall/beads · error

read %s: %w

Error message

read %s: %w

What it means

PersistedRemotes reads .dolt/repo_state.json under the database path to report configured Dolt remotes. If the file exists but cannot be read (permissions, I/O error), this error wraps the os.ReadFile failure with the path. A missing file is intentionally treated as 'no remotes' (nil, nil), so this error means the file exists but is unreadable.

Source

Thrown at internal/storage/doltutil/remotes.go:96

		strings.HasPrefix(url, "git+file://") ||
		strings.HasPrefix(url, "git://")
}

// PersistedRemotes reads the Dolt remotes recorded in
// <dbPath>/.dolt/repo_state.json directly, without shelling out to the dolt
// CLI — so it works when the dolt binary is absent and its failure modes are
// distinguishable (bd-6dnrw.33). A missing .dolt directory or repo_state.json
// means "not a dolt repository here" and returns (nil, nil); an unreadable or
// unparseable file returns an error so callers can tell "definitely none"
// from "could not tell". Results are sorted by name.
func PersistedRemotes(dbPath string) ([]storage.RemoteInfo, error) {
	path := filepath.Join(dbPath, ".dolt", "repo_state.json")
	data, err := os.ReadFile(path) // #nosec G304 -- repo-local dolt state file
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("read %s: %w", path, err)
	}
	var state struct {
		Remotes map[string]struct {
			URL string `json:"url"`
		} `json:"remotes"`
	}
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, fmt.Errorf("parse %s: %w", path, err)
	}
	remotes := make([]storage.RemoteInfo, 0, len(state.Remotes))
	for name, r := range state.Remotes {
		remotes = append(remotes, storage.RemoteInfo{Name: name, URL: r.URL})
	}
	sort.Slice(remotes, func(i, j int) bool { return remotes[i].Name < remotes[j].Name })
	return remotes, nil
}

// ListCLIRemotes parses `dolt remote -v` output from the given database

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions on <dbPath>/.dolt/repo_state.json and fix ownership/ACL (chown/chmod).
  2. Verify the filesystem is writable/readable and not mounted read-only.
  3. Close processes locking the file (backup/AV/indexers) and retry.
  4. As a last resort restore the file from a healthy clone or let dolt regenerate repo state.
Defensive patterns

Strategy: try-catch

Validate before calling

st, err := os.Stat(filepath.Join(dbPath, ".dolt", "repo_state.json"))
readable := err == nil && !st.IsDir()
// note: missing file is fine (returns nil remotes); only unreadable files error

Try / catch

remotes, err := doltutil.PersistedRemotes(dbPath)
if err != nil {
    return fmt.Errorf("cannot read dolt repo state: %w", err) // inspect path in message
}

Prevention

When it happens

Trigger: Calling PersistedRemotes when .dolt/repo_state.json exists but is unreadable: permission denied, file locked, or an I/O error (bad disk).

Common situations: Repo state file owned by another user (ran bd with sudo previously); read-only mount; antivirus/backup lock on the file.

Related errors


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