{"record":{"id":"10e019f3760be7d6","repo":"charmbracelet/crush","slug":"failed-to-create-gitignore-file-q-w-10e019","errorCode":null,"errorMessage":"failed to create .gitignore file: %q %w","messagePattern":"failed to create \\.gitignore file: %q %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/root.go","lineNumber":298,"sourceCode":"\t}\n\n\tstore, err := config.Init(cwd, dataDir, debug)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcfg := store.Config()\n\tstore.Overrides().SkipPermissionRequests = yolo\n\tstore.Overrides().EnabledChannels = channels\n\n\tif err := os.MkdirAll(cfg.Options.DataDirectory, 0o700); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to create data directory: %q %w\", cfg.Options.DataDirectory, err)\n\t}\n\n\tgitIgnorePath := filepath.Join(cfg.Options.DataDirectory, \".gitignore\")\n\tif _, err := os.Stat(gitIgnorePath); os.IsNotExist(err) {\n\t\tif err := os.WriteFile(gitIgnorePath, []byte(\"*\\n\"), 0o644); err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"failed to create .gitignore file: %q %w\", gitIgnorePath, err)\n\t\t}\n\t}\n\n\tif err := projects.Register(cwd, cfg.Options.DataDirectory); err != nil {\n\t\tslog.Warn(\"Failed to register project\", \"error\", err)\n\t}\n\n\tconn, err := db.Connect(ctx, cfg.Options.DataDirectory)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tlogFile := filepath.Join(cfg.Options.DataDirectory, \"logs\", \"crush.log\")\n\tcrushlog.Setup(logFile, debug)\n\n\t// Discover skills once before app.New. Local mode hosts a single\n\t// workspace per process, so WithGlobalMirror keeps the package\n\t// globals (which the TUI reads via skills.GetLatestStates) in sync","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/root.go#L280-L316","documentation":"setupLocalWorkspace in internal/cmd/root.go writes a `.gitignore` containing `*\\n` into the data directory so crush's state is never committed. If os.WriteFile fails (permissions, read-only fs, path is a directory), it returns 'failed to create .gitignore file: %q %w'. The data dir already existed or was created, so this is a secondary but blocking failure.","triggerScenarios":"The data directory exists but is not writable by the current user; `.gitignore` path is actually a directory; filesystem is read-only or full; restrictive umask/ACLs.","commonSituations":"Data directory created previously by another user (e.g. via sudo); syncing tools (Dropbox) locking files; container volumes mounted read-only after first run; SELinux/AppArmor denials.","solutions":["Fix ownership/permissions of the data directory: chown -R $(whoami) <dir> && chmod u+w <dir>","If <dir>/.gitignore is a directory, remove it (rm -rf) and retry","Manually create the file: echo '*' > <datadir>/.gitignore, then rerun","Mount the data directory writable if in a container"],"exampleFix":"// shell, before\nsudo crush run  # data dir now root-owned\n// after\nsudo chown -R $(whoami) ~/.local/share/crush\ncrush run","handlingStrategy":"validation","validationCode":"if err := os.Chmod(dataDir, 0o700); err != nil {\n    return fmt.Errorf(\"data dir %s not writable: %w\", dataDir, err)\n}\nos.WriteFile(filepath.Join(dataDir, \".gitignore\"), []byte(\"*\\n\"), 0o644)","typeGuard":null,"tryCatchPattern":"if err := os.WriteFile(gitIgnorePath, []byte(\"*\\n\"), 0o644); err != nil {\n    if errors.Is(err, fs.ErrPermission) {\n        // chown/chmod the data dir or recreate it as the current user\n    }\n    return err\n}","preventionTips":["Always run crush as the same user that owns the data directory","Never run the CLI under sudo for normal use","Check that .gitignore inside the data dir is not a directory"],"tags":["filesystem","permissions","gitignore","startup"],"backgroundTag":"mkdir-permission-denied","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}