{"record":{"id":"573440574929c70e","repo":"gastownhall/beads","slug":"reading-config-w","errorCode":null,"errorMessage":"reading config: %w","messagePattern":"reading config: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/configfile/configfile.go","lineNumber":114,"sourceCode":"\n\t\t// Migrate: parse legacy config, save as metadata.json, remove old file\n\t\tvar cfg Config\n\t\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"parsing legacy config: %w\", err)\n\t\t}\n\n\t\t// Save to new location\n\t\tif err := cfg.Save(beadsDir); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"migrating config to metadata.json: %w\", err)\n\t\t}\n\n\t\t// Remove legacy file (best effort: migration already saved to new location)\n\t\t_ = os.Remove(legacyPath)\n\n\t\treturn &cfg, nil\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading config: %w\", err)\n\t}\n\n\tvar cfg Config\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing config: %w\", err)\n\t}\n\n\treturn &cfg, nil\n}\n\n// LoadForDiscovery reads workspace metadata without migrating or rewriting it.\n//\n// Store admission uses this only to classify a workspace before a command can\n// open storage. In particular, legacy config.json remains in place so a failed\n// admission cannot turn a recoverable old workspace into a partially migrated\n// one. Unknown JSON fields remain tolerated here because callers use the\n// result only to decide whether to issue a conservative refusal; normal store\n// selection keeps its existing validation.","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/configfile/configfile.go#L96-L132","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n-rw------- 1 root root metadata.json   # bd run as non-root\n// after\n$ sudo chown $USER .beads/metadata.json && bd ready","handlingStrategy":"try-catch","validationCode":"info, err := os.Stat(filepath.Join(beadsDir, \"metadata.json\"))\nif err == nil && (!info.Mode().IsRegular() || info.Mode().Perm()&0o400 == 0) {\n\treturn fmt.Errorf(\"metadata.json unreadable or not a regular file\")\n}","typeGuard":null,"tryCatchPattern":"cfg, err := configfile.Load(beadsDir)\nvar pathErr *os.PathError\nif errors.As(err, &pathErr) && errors.Is(pathErr.Err, os.ErrPermission) {\n\treturn fmt.Errorf(\"fix ownership/permissions on %s: %w\", pathErr.Path, err)\n}","preventionTips":["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"],"tags":["filesystem","config","permissions","io"],"backgroundTag":"config-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}