Workstation Setup tutorial series — Part 3: Install and configure Claude Code
Claude Code in VS Code, sign-in, and the mandatory global settings and safety hooks that intercept destructive shell commands before they run. Short but important — running Claude Code without these hooks is how people accidentally wipe their own repo. Third of six short modules.
With the runtimes in place from Part 2, it's time to add the AI pair programmer we actually work with: Claude Code. This part covers the install, the sign-in, and — the bit you should not skip — the mandatory global settings and safety hooks that keep Claude from running destructive shell commands without your approval. Short part, important part.
Step 1: install the Claude Code extension
Inside your WSL-connected VS Code window:
Ctrl+Shift+Xto open Extensions.- Search for Claude Code by Anthropic.
- Click Install.
Or from the WSL terminal:
code --install-extension anthropic.claude-code
You should already have this from Part 2 (it was in the extension list). If so, just verify it shows up in the sidebar.
Step 2: sign in
Open the Claude Code panel from the VS Code sidebar (the small Claude icon). Click Sign in and follow the OAuth flow in your browser. Once signed in, the panel shows your subscription tier in the bottom-right.
Working from the terminal? Run
claude auth logininstead. This is needed if you want to use theclaudeCLI for skills like/opsx-apply-loopthat run inside Docker containers. Optional for now; come back to it once you're comfortable.
Try a quick test prompt: ask Claude something simple like "what's the current directory?" and confirm you get an answer. If you see a permission prompt for a Bash command — that's the first sign that the safety hooks aren't installed yet. Don't approve anything destructive; finish the next steps first.
Step 3: clone the .github repo
The global settings live in the canonical Conduction/.github repo on Codeberg. Clone it once:
mkdir -p ~/code/conduction
cd ~/code/conduction
git clone [email protected]:Conduction/.github.git
cd .github
No SSH key yet? Part 2 — Codeberg auth walks through it. As a fallback you can also clone over HTTPS:
git clone https://codeberg.org/Conduction/.github.git— that works without auth but won't let you push.
You only need to clone this once — the version-check hook fetches updates via the GitHub API after this (the settings repo is mirrored from Codeberg to ConductionNL/.github on GitHub for the hook), no further git pull needed.
Step 4: install the global settings + hooks
From inside the cloned .github repo:
REPO_ROOT="$(pwd)"
mkdir -p ~/.claude/hooks
cp "$REPO_ROOT/global-settings/settings.json" ~/.claude/settings.json
cp "$REPO_ROOT/global-settings/block-write-commands.sh" ~/.claude/hooks/block-write-commands.sh
cp "$REPO_ROOT/global-settings/block-config-tool-writes.sh" ~/.claude/hooks/block-config-tool-writes.sh
cp "$REPO_ROOT/global-settings/check-settings-version.sh" ~/.claude/hooks/check-settings-version.sh
chmod +x ~/.claude/hooks/*.sh
cp "$REPO_ROOT/global-settings/VERSION" ~/.claude/settings-version
echo "$REPO_ROOT" > ~/.claude/settings-repo-path
# Online version checking via GitHub API (recommended — no local repo required):
cp "$REPO_ROOT/global-settings/settings-repo-url.example" ~/.claude/settings-repo-url
That's the working set. What did you just install?
| File | What it does |
|---|---|
~/.claude/settings.json | The user-level permissions allowlist — which commands Claude can run without asking, which need approval, which are blocked. Plus references to the two hooks below. |
~/.claude/hooks/block-write-commands.sh | The big one. Runs before every Bash tool call. Detects write/destructive commands (rm, mv, git push --force, chmod, etc.) and prompts for explicit approval. |
~/.claude/hooks/block-config-tool-writes.sh | Runs before every Write/Edit/MultiEdit call. Refuses any tool that tries to write to ~/.claude/ or produce a script that would. Protects the hooks themselves from being silently rewritten. |
~/.claude/hooks/check-settings-version.sh | Runs on session start. Compares your installed version against ConductionNL/.github and warns you if you're behind. |
~/.claude/settings-repo-url | The repo slug the version check uses to fetch the canonical version (ConductionNL/.github). |
Tracking a non-default branch? Copy
settings-repo-ref.exampleto~/.claude/settings-repo-refand put your branch/tag/SHA in it. For most developers the default —main— is right.
Step 5: lock the hooks with chattr +i
This is the layer no Claude command can bypass. Even if every other guard fails — a hook gets disabled, a permission gets approved by accident — the kernel refuses the write:
sudo chattr +i ~/.claude/settings.json ~/.claude/hooks/*.sh ~/.claude/settings-version
chattr +i sets the immutable bit on Linux file systems. After this, only sudo chattr -i (which Claude can't run without prompting you) can clear it.
chattrnot supported? The Conduction setup assumes WSL2 on the standard ext4 file system, which supportschattr +i. If your environment doesn't (some non-ext4 file systems returnOperation not supported), skip this step — the hook layers from Step 4 still defend in depth. The kernel layer is the strongest of the four, so if you can runchattr, do.
Step 6: restart Claude Code
The hooks register at session start. Restart Claude Code — Ctrl+Shift+P → "Developer: Reload Window" in VS Code is a quick way — and start a new session.
On the next session start, Claude's first message should relay something like:
New session started — Global Claude Settings checked. Settings are up to date (v1.7.0).
If you instead see UPDATE REQUIRED, your installed version is behind main. To update:
sudo chattr -i ~/.claude/settings.json ~/.claude/hooks/*.sh ~/.claude/settings-version
Then ask Claude "update my global settings to <version>" — it pulls the new files from GitHub. After that, re-lock:
sudo chattr +i ~/.claude/settings.json ~/.claude/hooks/*.sh ~/.claude/settings-version
What's the difference between global and project settings?
A common point of confusion. Two layers:
| Layer | Lives at | Covers | Who maintains it |
|---|---|---|---|
| Global (user-level) | ~/.claude/settings.json + ~/.claude/hooks/ | Safety policy on your machine. Read-first, write-with-approval. Affects every project you open. | You — but from the canonical Conduction templates. Versioned in Conduction/.github on Codeberg (mirrored to ConductionNL/.github on GitHub for the version-check hook). |
| Project-level | .claude/settings.json inside each repo | Project-specific allowlist — which MCP servers to enable, which Bash commands are pre-approved for this project's tests. | The project maintainers; checked into the repo. |
The two complement each other. Project settings can pre-approve specific commands for that project's tests; they cannot loosen the global write-approval policy. If a project tries to allow rm -rf without a prompt, the global hook still catches it.
Which hooks are recommended, and what do they do?
The default Conduction install ships three hooks. In one sentence each:
block-write-commands.sh— everyBashcommand Claude wants to run gets checked first. Destructive or write-class commands (rm,mv,chmod,git push --force,npm publish, etc.) trigger an approval prompt; safe read-only commands pass through silently.block-config-tool-writes.sh— everyWrite/Edit/MultiEditClaude attempts gets checked first. If the target is inside~/.claude/, or the content would create a script that writes to~/.claude/, the hook refuses. This is what stops Claude from quietly disabling its own safety net.check-settings-version.sh— runs once at session start. Compares your installed version to the canonical repo and tells you (in the chat) whether you're up to date.
The first two are the safety net. The third is the heartbeat that keeps that safety net current.
Troubleshooting
Session-start panel says 'CONFIGURATION ERROR'Read the error line directly below the panel. Most common causes: gh not authenticated (run gh auth login), jq not installed (sudo apt install jq), or ~/.claude/settings-repo-url missing (recopy from global-settings/settings-repo-url.example).
chattr: Operation not supportedYour file system may not support the immutable attribute (ext4 inside WSL2 does; some non-ext4 file systems don't). Skip the immutable-lock step — the hook layer from Step 4 still defends in depth.
Claude offered to run rm -rf and no approval prompt appearedThe hooks didn't load. Confirm the files exist (ls -la ~/.claude/hooks/), are executable (chmod +x), and Claude Code was restarted after install. If still nothing, re-run Step 4 — odds are settings.json didn't get copied in.
Session-start line mentions a new session but no version number is shownThe version file is missing. Run cat ~/.claude/settings-version — if empty, recopy from global-settings/VERSION in the cloned repo.
Test yourself
Five short questions to check that this part landed. Stuck? Click Hint. Curious about the answer? Click Answer.
1. What is the difference between global and project-level Claude settings, and why do we need both?
Hint
Where each lives, what each controls, and how they relate when both are present.
Answer
Global settings live at ~/.claude/settings.json + ~/.claude/hooks/. They define the safety policy on your machine and apply to every project you open. The Conduction global settings (mandatory) enforce read-first, write-with-approval behaviour.
Project settings live at .claude/settings.json inside each repo. They define project-specific allowances: which MCP servers to enable, which test commands are pre-approved, which file paths are read-only for that project.
They complement each other. Project settings can pre-approve commands that the global policy also allows; they cannot loosen the global write-approval policy. The global hook always runs first.
2. The Conduction global-settings install ships two enforcement hooks, not one: block-write-commands.sh and block-config-tool-writes.sh. Why both? What does each catch that the other misses?
Hint
The two hooks operate on different Claude tool calls. One guards the Bash tool; the other guards the file-write tools. Think about what each tool can and can't do.
Answer
block-write-commands.shintercepts every Bash call. It catches Claude trying to runrm -rf,git push --force,chmod,npm publish, etc. — anything destructive that goes through a shell.block-config-tool-writes.shintercepts every Write/Edit/MultiEdit call. It catches Claude trying to write directly to~/.claude/(e.g. disabling its own hooks) or producing a script that would later do the same.
What each catches that the other misses: a Bash command can rm files but can't make a Write tool call. A Write tool can edit ~/.claude/settings.json directly without ever running a shell command. If only the Bash hook existed, Claude could rewrite the safety net without invoking Bash. If only the Write hook existed, Claude could rm -rf your repo without editing a file. Both are needed because Claude has two separate paths to a destructive action.
3. Why does Step 5 use chattr +i on top of the hooks, and what's the trade-off?
Hint
Defense in depth — what does the kernel guarantee that an in-process hook cannot?
Answer
chattr +i sets the immutable bit at the kernel level. Once set, no process — including any Claude command — can write to those files until sudo chattr -i clears it. That's the one layer no in-process hook can bypass.
The trade-off: when a legitimate update is needed, you have to sudo chattr -i first, then re-lock with sudo chattr +i afterwards. A small friction tax in exchange for a guarantee that the safety net itself can't be silently rewritten.
4. You've just installed the global hooks and restarted Claude Code. What's the fastest way to confirm the Bash hook is actually intercepting — without sitting around waiting for Claude to spontaneously suggest something destructive?
Hint
Two things to look for at session start, and one thing you can prompt Claude to do that's harmless but will trip the hook.
Answer
Two confirmations:
- Session-start message. Claude's first message should include "Global Claude Settings checked. Settings are up to date". If that line is missing, the version-check hook isn't loaded — meaning the write-blocker is probably also missing.
- Provoke an approval prompt deliberately. Ask Claude something innocuous that touches a write-class command, e.g. "check whether
rmis on my PATH" — the hook should intercept the Bash call and ask for approval. Decline the prompt; you just wanted to see the interception fire.
If the prompt doesn't appear, the hook didn't load — go back to Step 4 and confirm ~/.claude/settings.json was actually copied and references the hooks correctly.
5. Claude offers to run rm -rf ./node_modules and the command runs immediately — no approval prompt appears. What does that tell you, and what do you check first?
Hint
The hook layer is supposed to catch exactly this. If it didn't, one of three things is true — and only one of them is "everything's fine".
Answer
It tells you the Bash hook didn't run. Three possibilities, in order of likelihood:
- The hook isn't installed. Run
ls -la ~/.claude/hooks/and confirmblock-write-commands.shexists and is executable (+x). If missing, re-run Step 4. ~/.claude/settings.jsondoesn't reference the hook. The hook only runs ifsettings.jsonregisters it. Open the file and confirm thePreToolUseblock points atblock-write-commands.sh.- Claude Code was started before the hooks were installed. Hooks load at session start. Reload the VS Code window (
Ctrl+Shift+P→ "Developer: Reload Window") and re-try.
Until one of those three is fixed, you're effectively running Claude with no safety net. Don't dismiss this as a one-off — investigate before continuing.
Next step
Claude Code is wired up and the safety net is in place. Time to give Claude direct access to the Conduction systems via the MCP server.