[Bug] "Allow always" saves full command text leaking secrets and causing parse errors
Bug Description
Bug: "Allow always" saves full command text as permission pattern, leaking secrets
Summary
When a user clicks "Allow always" on a permission prompt, Claude Code saves the entire command text verbatim into .claude/settings.local.json. For short commands like git add . this works fine, but for long commands (heredoc appends, inline scripts, commit messages with bodies, env-prefixed commands with API keys), this creates three problems:
- Secrets leak into the settings file — API keys, tokens, passwords, and JWTs embedded in one-off commands get persisted in plaintext
- Invalid patterns — Multi-line commands containing
:followed by*(e.g., file paths likedecisions/log.md) are parsed as pattern separators, causing settings file parse errors on next startup - Unbounded file growth — The file accumulates hundreds of entries that will never match again, since they're full commands rather than reusable patterns
Reproduction
- Start a Claude Code session in a project
- Run a command that includes a heredoc, e.g.:
````
cat >> docs/sessions/log.md << 'EOF'
### Session entry with decisions/log.md reference
EOF
- When prompted, click "Allow always"
- Inspect
.claude/settings.local.json— the entire heredoc is saved as one permission entry - Restart Claude Code — parse error on
decisions/log.mdbecause:*inside the path is interpreted as a pattern
Real-world example
After ~2 months of use, my settings.local.json had grown to 311 entries (31KB) including:
- Full Stripe live secret keys (
sk_live_...) - Supabase service role JWTs
- HeyGen API keys
- Vercel API tokens
- Supabase Postgres passwords
- Complete git commit messages with heredocs
- Inline Node.js scripts (50+ lines)
- Bash for-loop fragments (
while read f,do echo,done)
Two entries containing heredocs with paths like decisions/log.md caused a parse error that blocked Claude Code from starting.
Expected behavior
"Allow always" should extract a reusable prefix pattern from the command rather than saving it verbatim. For example:
| Command | Current (saved verbatim) | Expected (extracted pattern) |
|---------|--------------------------|------------------------------|
| cat >> docs/log.md << 'EOF'... | Full heredoc text | Bash(cat:*) or not saved at all |
| STRIPE_SECRET_KEY_LIVE=sk_live_... npx tsx script.ts | Full command with key | Bash(STRIPE_SECRET_KEY_LIVE=:*) or prompt to not save |
| git commit -m "$(cat <<'EOF'...full message...EOF)" | Full commit message | Bash(git commit:*) (already exists) |
| node -e "const OpenAI = require...50 lines..." | Full inline script | Bash(node -e:*) or not saved |
Suggested fixes
- Length limit: Don't save commands longer than ~100 characters as "Allow always" patterns. Show a warning instead: "This command is too long to save as a reusable pattern."
- Secret detection: Scan for patterns that look like API keys/tokens (e.g.,
sk_live_,sk-ant-,eyJhbG,whsec_) and refuse to save them, or redact to a prefix pattern. - Pattern extraction: For commands with env var prefixes (
KEY=value command), extractBash(KEY=:*)instead of saving the full value. - Deduplication: Before saving, check if a broader pattern already covers the command (e.g.,
git commit:*already covers any specificgit commit -m "..."command).
Additional note
settings.local.json probably belongs in Claude Code's default .gitignore template, since it's machine-specific and can accumulate secrets. Currently it's not gitignored by default.
Environment
- Claude Code (latest as of Feb 2026)
- macOS, Warp terminal
- ~2 months of active use on a Next.js project
Environment Info
- Platform: darwin
- Terminal: WarpTerminal
- Version: 2.1.39
- Feedback ID: b8c2b038-17e3-4070-a23e-a341dcb0c777
Errors
[{"error":"Error: ENOENT: no such file or directory, open '/Users/emmanuel/.claude/remote-settings.json'\n at openSync (unknown)\n at <anonymous> (/$bunfs/root/claude:12:947)\n at B2 (/$bunfs/root/claude:11:49917)\n at readSync (/$bunfs/root/claude:12:875)\n at $2 (/$bunfs/root/claude:6133:113)\n at Q5 (/$bunfs/root/claude:6135:226)\n at S9A (/$bunfs/root/claude:178:8273)\n at s7R (/$bunfs/root/claude:178:8436)\n at ZFD (/$bunfs/root/claude:179:1202)\n at H9T (/$bunfs/root/claude:179:2080)","timestamp":"2026-02-11T13:15:11.567Z"},{"error":"Error: NON-FATAL: Lock acquisition failed for /Users/emmanuel/.local/share/claude/versions/2.1.39 (expected in multi-process scenarios)\n at wVR (/$bunfs/root/claude:3626:2098)\n at DjA (/$bunfs/root/claude:3626:1209)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-02-11T13:15:…
Note: Content was truncated.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗