{"record":{"id":"c0349e96115d0bfa","repo":"gastownhall/beads","slug":"failed-to-write-config-yaml-w-c0349e","errorCode":null,"errorMessage":"failed to write config.yaml: %w","messagePattern":"failed to write config\\.yaml: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/repos.go","lineNumber":152,"sourceCode":"\t\t\t&yaml.Node{Kind: yaml.ScalarNode, Value: \"repos\"},\n\t\t\treposNode,\n\t\t)\n\t}\n\n\t// Marshal back to YAML\n\tvar buf strings.Builder\n\tencoder := yaml.NewEncoder(&buf)\n\tencoder.SetIndent(2)\n\tif err := encoder.Encode(&root); err != nil {\n\t\treturn fmt.Errorf(\"failed to encode config.yaml: %w\", err)\n\t}\n\tif err := encoder.Close(); err != nil {\n\t\treturn fmt.Errorf(\"failed to close encoder: %w\", err)\n\t}\n\n\t// Write back to file\n\tif err := os.WriteFile(configPath, []byte(buf.String()), 0600); err != nil {\n\t\treturn fmt.Errorf(\"failed to write config.yaml: %w\", err)\n\t}\n\n\t// Reload viper config so changes take effect immediately\n\tif v != nil {\n\t\tif err := v.ReadInConfig(); err != nil {\n\t\t\t// Not fatal - config is on disk, will be picked up on next command\n\t\t\t_ = err // Best effort: viper reload failure is non-fatal since config was already written to disk\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// buildReposNode creates a yaml.Node for the repos configuration\n// Returns nil if repos is empty (no primary and no additional)\nfunc buildReposNode(repos *ReposConfig) *yaml.Node {\n\tif repos == nil || (repos.Primary == \"\" && len(repos.Additional) == 0) {\n\t\treturn nil","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/config/repos.go#L134-L170","documentation":"The final step of SetReposInYAML writes the re-encoded YAML back to config.yaml with os.WriteFile using mode 0600. If the write fails, the error is wrapped as \"failed to write config.yaml\". Note the write happens only after encode+close succeed, so the data itself is valid.","triggerScenarios":"os.WriteFile(configPath, ...) fails — directory not writable, disk full, permission denied (existing file owned by another user — WriteFile does not chown), or read-only filesystem.","commonSituations":"Running bd as a different user than the one who owns config.yaml (0600 means only the owner can write), disk quota exceeded, or config directory mounted read-only (e.g. container with read-only volume).","solutions":["Check permissions on config.yaml and its parent directory; chown/chmod as needed","Free disk space or check quota (df -h, quota)","Ensure the filesystem is writable (not a read-only mount)","Delete and recreate the file if ownership is wrong (a new file gets correct owner)"],"exampleFix":"// before\n// file owned by root, running as user -> permission denied\n// after\nsudo chown $(whoami) ~/.config/bd/config.yaml\nbd repo add /path/to/repo","handlingStrategy":"validation","validationCode":"d := filepath.Dir(configPath)\nif fi, err := os.Stat(d); err != nil || !fi.IsDir() || fi.Mode().Perm()&0200 == 0 {\n    return fmt.Errorf(\"config dir %s not writable\", d)\n}","typeGuard":"func isWritableDir(p string) bool {\n    fi, err := os.Stat(p)\n    return err == nil && fi.IsDir() && fi.Mode().Perm()&0200 != 0\n}","tryCatchPattern":"if err := SetReposInYAML(cfgPath, repos); err != nil {\n    if strings.Contains(err.Error(), \"failed to write\") {\n        log.Printf(\"check ownership/permissions/disk on %s: %v\", cfgPath, err)\n    }\n    return err\n}","preventionTips":["Ensure the same OS user owns config.yaml and runs bd","Keep config directories writable (avoid read-only mounts for mutable config)","Monitor disk space/quota","Prefer recreating the file over chmod-ing foreign-owned files"],"tags":["filesystem","permissions","config"],"backgroundTag":"config-file-write-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}