Permission prompts and environment variables aren't a UI toggle in LingCode — they live in a real file at ~/.claude/settings.json. Once you know where it is and what shape it has, you can stop clicking "approve" on ls for the fiftieth time today.
By default LingCode prompts before every Bash command, every file write outside the project root, and every tool with side effects. That's the safe default — you don't want a fresh agent silently running rm -rf in your home directory.
But after using LingCode for a week you'll notice the same prompts pile up: ls, git status, cat package.json, npm test. None of those need approval. Each one costs 2–3 seconds and breaks your focus. Multiply by 50 a day.
The fix isn't a hidden menu — it's a JSON file you own and edit. Two locations:
~/.claude/settings.json — your user settings. Applies to every project on this Mac.<repo>/.claude/settings.local.json — project settings. Applies only inside that repo and overrides the user file.Three top-level keys matter for this tutorial: permissions.allow, permissions.deny, and env. (There's a fourth — hooks — which has its own tutorial; see Add custom hooks.)
In a terminal:
mkdir -p ~/.claude
touch ~/.claude/settings.json
open -e ~/.claude/settings.json
If the file is brand-new and empty, paste this skeleton and save:
{
"permissions": {
"allow": [],
"deny": []
},
"env": {}
}
LingCode picks the file up on the next prompt — no restart needed.
Each entry in allow and deny is a string matching a tool call. Two common shapes:
"Bash(<command pattern>)" — match a shell command. The pattern supports a literal command or a prefix glob."<ToolName>" — match any invocation of that tool, regardless of arguments. Useful for read-only tools you trust unconditionally.Example:
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(git status)",
"Bash(git diff:*)",
"Bash(npm test:*)",
"Read",
"Glob",
"Grep"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(sudo:*)"
]
}
}
Bash(ls:*) allows any invocation starting with ls — ls, ls -la, ls src/. Bash(git status) is exact-match only.
The two files merge, with the project file overriding the user file. Use this split:
~/.claude/settings.json): commands safe in any repo — ls, cat, git status, git diff, git log, pwd, which. These don't depend on the project.<repo>/.claude/settings.local.json): commands specific to this codebase — npm test, cargo build, ./scripts/build.sh, xcodebuild build. Different repos need different allowlists.The project file should be gitignored if it contains anything reflecting your personal workflow. If you want to share a baseline allowlist with your team, commit a .claude/settings.json (without .local) at the repo root — LingCode reads that too, and project + local layer on top.
The env block sets variables that LingCode (and the commands it runs) sees. Useful for:
DEBUG=1, NODE_ENV=development, NO_COLOR=1.JAVA_HOME, ANDROID_HOME if your shell rc doesn't already export them.{
"env": {
"NODE_ENV": "development",
"NO_COLOR": "1",
"PYTHONDONTWRITEBYTECODE": "1"
}
}
.claude/settings.json is often committed; reach for the Keychain or shell rc instead. Project local settings (settings.local.json) are gitignored by default and are the right place for per-machine secrets.
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(pwd)",
"Bash(which:*)",
"Bash(file:*)",
"Bash(stat:*)"
"Bash(git status)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git branch:*)",
"Bash(git show:*)",
"Bash(git remote -v)"
"Bash(npm test:*)",
"Bash(npm run lint:*)",
"Bash(npm run typecheck:*)",
"Bash(npx tsc --noEmit:*)",
"Bash(node --version)"
"Bash(swift build:*)",
"Bash(swift test:*)",
"Bash(xcodebuild build:*)",
"Bash(xcodebuild test:*)",
"Bash(xcrun simctl list)"
Drop these into permissions.allow for the project type that fits, then add to the list over the next few days as you notice approvals you keep granting.
The point of broad allow rules is they're forgiving. The point of deny rules is they're absolute. A short, high-confidence deny list:
"permissions": {
"deny": [
"Bash(rm -rf:*)",
"Bash(rm -fr:*)",
"Bash(sudo:*)",
"Bash(curl * | sh)",
"Bash(curl * | bash)",
"Bash(:(){ :|:& };:)",
"Bash(dd if=:*)",
"Bash(mkfs:*)",
"Bash(:>/dev/sda:*)"
]
}
Even with permissions.allow: ["Bash(*)"], the deny list still fires. This is the floor — set it once and forget it.
After editing, ask LingCode in chat:
Run `git status` and tell me whether you were prompted.
If your allowlist took effect, LingCode runs the command and reports the output without an approval dialog. If you still get the prompt, two likely causes:
python3 -m json.tool ~/.claude/settings.json — it prints a parse error if so."Bash(git status)" matches exactly git status; LingCode may have run git status --short. Switch to "Bash(git status:*)".Don't try to write the perfect allowlist upfront. The right workflow:
If you'd rather not do this by hand, there's a separate tutorial that scans your past transcripts and proposes an allowlist based on what you've actually approved: Reduce permission prompts by allowlisting safe commands.
The whole workflow — locate, shape, scope user vs project, verify — is packaged as a skill. Drop it into your skills folder and ask LingCode for "configure settings.json" to invoke it:
---
name: update-config
description: Configure the Claude Code harness via settings.json — permissions allow/deny and env vars (NOT hooks; hooks have their own skill). Triggers: 'allow X command', 'add permission to allowlist', 'set DEBUG=true', 'environment variable for Claude Code', 'move permission to user settings', 'configure settings.json', 'permissions config'. Actions: edit ~/.claude/settings.json (user) or .claude/settings.local.json (project), append to permissions.allow / permissions.deny, set env keys. File shape: JSON with permissions, env, hooks. Skip for: theme/model switches (use /config command), hook setup (use add-custom-hooks skill).
---
Configure LingCode by editing settings.json directly. Settings live in
two places that merge:
- User file: ~/.claude/settings.json (applies to every project)
- Project file: <repo>/.claude/settings.local.json (this repo only;
overrides user file)
For permission changes:
1. Open the relevant file. Create it with skeleton {permissions:
{allow: [], deny: []}, env: {}} if it doesn't exist.
2. Identify the exact command string the user wants allowed or denied.
Bash commands use "Bash(<pattern>)" — pattern is literal or
prefix-glob with ":*".
3. Add to permissions.allow or permissions.deny. Deny always wins.
4. For tools other than Bash, use the tool name directly: "Read",
"Glob", "Grep".
For env var changes:
1. Add to the env object as "KEY": "value" strings.
2. Long-lived secrets go in settings.local.json (gitignored), not the
shared settings.json.
After editing, validate with `python3 -m json.tool` and ask LingCode
to run a sample command to confirm the rule took effect. If the
prompt still appears, the most likely cause is a glob mismatch — read
the dialog's exact command string and write the pattern to match it.
Scope user vs project deliberately: read-only universal commands (ls,
cat, git status) belong in the user file; project-specific build and
test commands belong in the project file.
Save as ~/.lingcode/skills/update-config/SKILL.md — see Install a skill for the exact location and how skills get discovered.