gastownhall/beads · error
refusing to write secret key %q to git-tracked config file %
Error message
refusing to write secret key %q to git-tracked config file %s This would expose your secret in git history. Instead: export %s="your-key-here" # add to ~/.secrets or ~/.zshrc Or move config.yaml out of git tracking: git rm --cached %s echo "config.yaml" >> %s/.gitignore To override this check (e.g., for testing): bd config set --force-git-tracked %s "value"
What it means
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.
Source
Thrown at internal/config/yaml_config.go:233
configPath, err := findProjectConfigYaml()
if err != nil {
return nil // can't resolve path; let the write fail with its own error
}
return checkSecretGitTracked(configPath, key)
}
func checkSecretGitTracked(configPath, key string) error {
if !IsYamlOnlyKey(key) {
return nil
}
if !IsSecretKey(key) {
return nil
}
if !isGitTracked(configPath) {
return nil
}
envVar := secretKeyEnvVarHint(key)
return fmt.Errorf(
"refusing to write secret key %q to git-tracked config file %s\n\n"+
"This would expose your secret in git history. Instead:\n"+
" export %s=\"your-key-here\" # add to ~/.secrets or ~/.zshrc\n\n"+
"Or move config.yaml out of git tracking:\n"+
" git rm --cached %s\n"+
" echo \"config.yaml\" >> %s/.gitignore\n\n"+
"To override this check (e.g., for testing):\n"+
" bd config set --force-git-tracked %s \"value\"",
key, configPath,
envVar,
configPath,
filepath.Dir(configPath),
key,
)
}
// keyAliases maps alternative key names to their canonical yaml form.
// This ensures consistency when users use different formats (dot vs hyphen).View on GitHub (pinned to 71377f2769)
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.
Example fix
// before bd config set secret.anthropic_key sk-ant-... # config.yaml is git-tracked -> refused // after export ANTHROPIC_KEY=sk-ant-... git rm --cached .beads/config.yaml echo ".beads/config.yaml" >> .gitignore
Defensive patterns
Strategy: validation
Validate before calling
func isGitTracked(path string) bool {
return exec.Command("git", "ls-files", "--error-unmatch", path).Run() == nil
}
if isGitTracked(configPath) && strings.HasPrefix(key, "secret.") {
os.Setenv(secretEnvVar(key), value) // use env instead
return nil
} Type guard
func safeForConfigWrite(key, configPath string) bool {
return !strings.HasPrefix(key, "secret.") || !isGitTracked(configPath)
} Try / catch
err := cfg.CheckSecretKeyGitSafety(key, configPath)
if err != nil && strings.Contains(err.Error(), "refusing to write secret key") {
// fall back to env var per the error's remediation
os.Setenv(envHint, value)
return nil
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to configure git hooks path: %w
- unsafe path %q in porcelain status
- BEADS_DIR points to unsafe location: %s
- --allowed-host is empty; pass the Host header value clients
- identity: oversized reply
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3bf57f291de727b1.
Report an issue: GitHub.