{"record":{"id":"3bf57f291de727b1","repo":"gastownhall/beads","slug":"refusing-to-write-secret-key-q-to-git-tracked-con","errorCode":null,"errorMessage":"refusing to write secret key %q to git-tracked config file %s\n\nThis would expose your secret in git history. Instead:\n  export %s=\"your-key-here\"    # add to ~/.secrets or ~/.zshrc\n\nOr move config.yaml out of git tracking:\n  git rm --cached %s\n  echo \"config.yaml\" >> %s/.gitignore\n\nTo override this check (e.g., for testing):\n  bd config set --force-git-tracked %s \"value\"","messagePattern":"refusing to write secret key %q to git-tracked config file (.+?)\n\nThis would expose your secret in git history\\. Instead:\n  export (.+?)=\"your-key-here\"    # add to ~/\\.secrets or ~/\\.zshrc\n\nOr move config\\.yaml out of git tracking:\n  git rm --cached (.+?)\n  echo \"config\\.yaml\" >> (.+?)/\\.gitignore\n\nTo override this check \\(e\\.g\\., for testing\\):\n  bd config set --force-git-tracked (.+?) \"value\"","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/yaml_config.go","lineNumber":233,"sourceCode":"\tconfigPath, err := findProjectConfigYaml()\n\tif err != nil {\n\t\treturn nil // can't resolve path; let the write fail with its own error\n\t}\n\treturn checkSecretGitTracked(configPath, key)\n}\n\nfunc checkSecretGitTracked(configPath, key string) error {\n\tif !IsYamlOnlyKey(key) {\n\t\treturn nil\n\t}\n\tif !IsSecretKey(key) {\n\t\treturn nil\n\t}\n\tif !isGitTracked(configPath) {\n\t\treturn nil\n\t}\n\tenvVar := secretKeyEnvVarHint(key)\n\treturn fmt.Errorf(\n\t\t\"refusing to write secret key %q to git-tracked config file %s\\n\\n\"+\n\t\t\t\"This would expose your secret in git history. Instead:\\n\"+\n\t\t\t\"  export %s=\\\"your-key-here\\\"    # add to ~/.secrets or ~/.zshrc\\n\\n\"+\n\t\t\t\"Or move config.yaml out of git tracking:\\n\"+\n\t\t\t\"  git rm --cached %s\\n\"+\n\t\t\t\"  echo \\\"config.yaml\\\" >> %s/.gitignore\\n\\n\"+\n\t\t\t\"To override this check (e.g., for testing):\\n\"+\n\t\t\t\"  bd config set --force-git-tracked %s \\\"value\\\"\",\n\t\tkey, configPath,\n\t\tenvVar,\n\t\tconfigPath,\n\t\tfilepath.Dir(configPath),\n\t\tkey,\n\t)\n}\n\n// keyAliases maps alternative key names to their canonical yaml form.\n// This ensures consistency when users use different formats (dot vs hyphen).","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/config/yaml_config.go#L215-L251","documentation":"checkSecretGitTracked refuses to persist a secret-type config key (e.g. secret.* keys) into a config.yaml that is tracked by git, because the value would leak into git history. It throws a detailed error with remediation (env var, git rm --cached, .gitignore) and an explicit --force-git-tracked override. This is a deliberate safety guard, not a bug.","triggerScenarios":"Calling CheckSecretKeyGitSafety / the config writer (bd config set) for a secret key while configPath is inside a git repo and `git ls-files` shows config.yaml is tracked.","commonSituations":"Committing config.yaml with secret.* keys in a project repo; initializing bd inside a repo where config.yaml was previously committed; team-shared repos where users keep keys in config instead of the environment.","solutions":["Store the key in the environment instead: export <ENV_VAR>=\"your-key-here\" (the error names the exact env var via secretKeyEnvVarHint).","Untrack the file: git rm --cached <config.yaml> and add it to .gitignore, then rewrite.","Re-run with bd config set --force-git-tracked <key> \"value\" only if you consciously accept the exposure (e.g. test fixtures).","Rotate the key if it was ever committed to history (git filter-repo / BFG) since the guard may have been bypassed previously."],"exampleFix":"// before\nbd config set secret.anthropic_key sk-ant-...   # config.yaml is git-tracked -> refused\n// after\nexport ANTHROPIC_KEY=sk-ant-...\ngit rm --cached .beads/config.yaml\necho \".beads/config.yaml\" >> .gitignore","handlingStrategy":"validation","validationCode":"func isGitTracked(path string) bool {\n    return exec.Command(\"git\", \"ls-files\", \"--error-unmatch\", path).Run() == nil\n}\nif isGitTracked(configPath) && strings.HasPrefix(key, \"secret.\") {\n    os.Setenv(secretEnvVar(key), value) // use env instead\n    return nil\n}","typeGuard":"func safeForConfigWrite(key, configPath string) bool {\n    return !strings.HasPrefix(key, \"secret.\") || !isGitTracked(configPath)\n}","tryCatchPattern":"err := cfg.CheckSecretKeyGitSafety(key, configPath)\nif err != nil && strings.Contains(err.Error(), \"refusing to write secret key\") {\n    // fall back to env var per the error's remediation\n    os.Setenv(envHint, value)\n    return nil\n}","preventionTips":["Keep secret.* keys in environment variables or a secrets manager, never in config.yaml.","Add .beads/config.yaml to .gitignore in every repo using bd.","Never pass --force-git-tracked for real credentials; reserve it for synthetic test values.","Periodically scan git history for accidentally committed keys and rotate them."],"tags":["security","git","secrets","config"],"backgroundTag":"secret-committed-to-git","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}