{"record":{"id":"76eda0a88e020dd1","repo":"gastownhall/beads","slug":"fs-writeprojectgitignore-workdir-not-set","errorCode":null,"errorMessage":"fs: WriteProjectGitignore: workDir not set","messagePattern":"fs: WriteProjectGitignore: workDir not set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/fs/beads.go","lineNumber":145,"sourceCode":"\t}\n\tvar missing []string\n\tfor _, line := range strings.Split(template, \"\\n\") {\n\t\ttrimmed := strings.TrimSpace(line)\n\t\tif trimmed == \"\" || strings.HasPrefix(trimmed, \"#\") || have[trimmed] {\n\t\t\tcontinue\n\t\t}\n\t\tmissing = append(missing, trimmed)\n\t}\n\treturn missing\n}\n\nfunc (r *beadsDirFSRepositoryImpl) BeadsGitignoreExists(ctx context.Context) (bool, error) {\n\treturn fileExists(filepath.Join(r.beadsDir, \".gitignore\"), \"fs: BeadsGitignoreExists\")\n}\n\nfunc (r *beadsDirFSRepositoryImpl) WriteProjectGitignore(ctx context.Context) error {\n\tif r.workDir == \"\" {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: workDir not set\")\n\t}\n\tif len(r.templates.ProjectGitignorePatterns) == 0 {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: patterns not configured\")\n\t}\n\tpath := filepath.Join(r.workDir, \".gitignore\")\n\t// #nosec G304 -- path joined under bound workDir\n\texisting, err := os.ReadFile(path)\n\tif err != nil && !errors.Is(err, os.ErrNotExist) {\n\t\treturn fmt.Errorf(\"fs: WriteProjectGitignore: read: %w\", err)\n\t}\n\n\tvar toAdd []string\n\tfor _, pattern := range r.templates.ProjectGitignorePatterns {\n\t\tif !containsLine(existing, pattern) {\n\t\t\ttoAdd = append(toAdd, pattern)\n\t\t}\n\t}\n\tif len(toAdd) == 0 {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/fs/beads.go#L127-L163","documentation":"WriteProjectGitignore manages bd-required patterns in the project root .gitignore. This sentinel error means the repository was built without a workDir, so bd has no project root to place the gitignore in. It is a wiring/configuration error, not a filesystem failure.","triggerScenarios":"Calling WriteProjectGitignore on a beadsDirFSRepositoryImpl whose workDir field is empty — the constructor was not given the project root.","commonSituations":"Programmatic use of the fs repository without WithWorkDir-style option; tests instantiating the impl directly; embedding bd as a library without resolving the project root first.","solutions":["Construct the repository through its standard constructor, passing the project root/workDir option","If wiring manually, set workDir to an absolute project path before calling the method","Resolve the git root (git rev-parse --show-toplevel) and pass that as workDir when unsure","Verify initialization order: workDir must be bound before any gitignore write is attempted"],"exampleFix":"// before\nr := &beadsDirFSRepositoryImpl{beadsDir: dir} // workDir empty\nr.WriteProjectGitignore(ctx) // workDir not set\n// after\nworkDir, _ := gitRoot(ctx)\nr := fsrepo.New(fsrepo.WithBeadsDir(dir), fsrepo.WithWorkDir(workDir))\nr.WriteProjectGitignore(ctx)","handlingStrategy":"validation","validationCode":"type worker interface{ HasWorkDir() bool }\nif w, ok := repo.(worker); !ok || !w.HasWorkDir() {\n\treturn errors.New(\"repo requires workDir before WriteProjectGitignore\")\n}\nrepo.WriteProjectGitignore(ctx)","typeGuard":"func hasWorkDir(w string) bool { return w != \"\" }","tryCatchPattern":"if err := repo.WriteProjectGitignore(ctx); err != nil {\n\tif strings.Contains(err.Error(), \"workDir not set\") {\n\t\treturn fmt.Errorf(\"rebuild repository with fsrepo.WithWorkDir(gitRoot)\")\n\t}\n\treturn err\n}","preventionTips":["Bind workDir at repository construction time, before any write calls","Resolve the git root programmatically rather than assuming cwd","In library/embedding contexts, pass the project root explicitly","Add an early sanity call that fails fast when workDir is empty"],"tags":["configuration","initialization","gitignore"],"backgroundTag":"missing-config-value","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}