gastownhall/beads · error

reading config: %w

Error message

reading config: %w

What it means

Load() reads .beads/metadata.json; this error wraps a non-IsNotExist os.ReadFile failure on that file (e.g. permission denied, EISDIR, I/O error). It is not about the file being absent — missing metadata.json falls through to the legacy config.json path and returns nil,nil if neither exists.

Source

Thrown at internal/configfile/configfile.go:114

		// Migrate: parse legacy config, save as metadata.json, remove old file
		var cfg Config
		if err := json.Unmarshal(data, &cfg); err != nil {
			return nil, fmt.Errorf("parsing legacy config: %w", err)
		}

		// Save to new location
		if err := cfg.Save(beadsDir); err != nil {
			return nil, fmt.Errorf("migrating config to metadata.json: %w", err)
		}

		// Remove legacy file (best effort: migration already saved to new location)
		_ = os.Remove(legacyPath)

		return &cfg, nil
	}
	if err != nil {
		return nil, fmt.Errorf("reading config: %w", err)
	}

	var cfg Config
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("parsing config: %w", err)
	}

	return &cfg, nil
}

// LoadForDiscovery reads workspace metadata without migrating or rewriting it.
//
// Store admission uses this only to classify a workspace before a command can
// open storage. In particular, legacy config.json remains in place so a failed
// admission cannot turn a recoverable old workspace into a partially migrated
// one. Unknown JSON fields remain tolerated here because callers use the
// result only to decide whether to issue a conservative refusal; normal store
// selection keeps its existing validation.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check permissions: ls -l .beads/metadata.json, then chmod u+r
  2. Verify metadata.json is a regular file, not a directory or broken symlink
  3. Run bd as the user who owns the workspace (avoid mismatched sudo)
  4. Check filesystem health (dmesg / mount status) if on NFS or failing disk

Example fix

// before
-rw------- 1 root root metadata.json   # bd run as non-root
// after
$ sudo chown $USER .beads/metadata.json && bd ready
Defensive patterns

Strategy: try-catch

Validate before calling

info, err := os.Stat(filepath.Join(beadsDir, "metadata.json"))
if err == nil && (!info.Mode().IsRegular() || info.Mode().Perm()&0o400 == 0) {
	return fmt.Errorf("metadata.json unreadable or not a regular file")
}

Try / catch

cfg, err := configfile.Load(beadsDir)
var pathErr *os.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr.Err, os.ErrPermission) {
	return fmt.Errorf("fix ownership/permissions on %s: %w", pathErr.Path, err)
}

Prevention

When it happens

Trigger: Calling configfile.Load(beadsDir) when metadata.json exists but the process lacks read permission, metadata.json is actually a directory, or the underlying filesystem returns an I/O error.

Common situations: metadata.json with 000 permissions after a bad chmod; running bd as a different user (e.g. via sudo or CI) than the workspace owner; metadata.json replaced by a symlink to a directory; network filesystem outages.

Related errors


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