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
- Check permissions: ls -l .beads/metadata.json, then chmod u+r
- Verify metadata.json is a regular file, not a directory or broken symlink
- Run bd as the user who owns the workspace (avoid mismatched sudo)
- 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
- Run bd as the workspace owner, not via mismatched sudo
- Never chmod 000 files in .beads
- Check symlink targets before replacing metadata.json
- Align CI container user UID/GID with the host workspace owner
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
- failed to read config.yaml: %w
- write metadata.json: %w
- failed to save config: %w
- failed to set beads.role config: %w
- failed to write config.yaml: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/573440574929c70e.
Report an issue: GitHub.