Setting Up Claude Code on a Live Site, With Guardrails

Write-up ·

Summary

I built cyberhippo.ca with Claude chat, copying Git patches into my repo by hand. This time I installed Claude Code and let it work in the repo directly. This site is live: every push to main redeploys it through Cloudflare Pages. So before it touched anything, I set limits on what it could do.

Its first task was an accessibility and link audit. It found some problems and made five small fixes. I reviewed every change and made the signed commits myself. It also got a few things wrong, which are listed below.

The plan

The plan: pieces, choices and reasons
PieceChoiceWhy
InstallNative installer on my MacBook ProNo Node.js needed, and it updates itself
AccountMy Claude Pro planNot pay-per-use API billing
InstructionsCLAUDE.md in the repoClaude Code reads it at the start of every session
First taskAudit pages, list findings, no changesSee what it finds before it edits anything
CommitsMe, signed with my SSH keyA signature should mean I reviewed the change

Install and sign in

curl -fsSL https://claude.ai/install.sh | bash
claude --version
claude doctor

On first launch the header said "API Usage Billing", and the default mode was auto mode, which runs actions it considers low-risk without asking. I signed in with /login using my Pro account, and changed the mode so it asks before making changes.

CLAUDE.md

Running /init made Claude Code read the repo and write a CLAUDE.md file. It got the important parts right:

One line said "only commit/push when asked." That still leaves room for a commit, so I changed it to "never commit or push" and added a Rules section:

The audit

I used Plan mode for the audit, so Claude Code could read files but not change them. I asked it to check for broken internal links, missing alt text and WCAG 2.1 AA issues. Because I was close to my weekly usage limit, I limited the audit to two pages: the home page and the Windows Home remote access write-up.

It reported:

It also listed what it couldn't check from the source alone, like how pages actually render, zoom and screen reader output.

I approved five fixes: the date, the meta description, table captions, the duplicate header block and the footer arrow. I left the heading and skip link fixes for later because they need a CSS change on every page. I kept the tabindex on code blocks on purpose, because scrollable blocks should be reachable by keyboard.

Review and commit

I reviewed every change with git diff, made two signed commits (one for CLAUDE.md, one for the fixes) and pushed. Then I checked the live site with curl: the new date was live and the image header I'd touched in _headers was still correct.

Locking it down

Later I connected the GitHub CLI. Its token can read and write all my repos, and Claude Code can run gh commands. A rule in CLAUDE.md is an instruction, not a block, so I added a deny list in .claude/settings.json:

{
  "permissions": {
    "deny": [
      "Bash(git commit:*)",
      "Bash(git push:*)",
      "Bash(git reset:*)",
      "Bash(git rebase:*)",
      "Bash(gh pr merge:*)",
      "Bash(gh repo delete:*)",
      "Bash(gh api:*)"
    ]
  }
}

To test it, I asked Claude Code to run git push --dry-run. It was rejected with no prompt. That gives three layers: a mode that asks before changes, the rules in CLAUDE.md, and the deny list.

What it got wrong

What Claude Code got wrong
ProblemWhat happened
Wrong review commandIt told me to review the new CLAUDE.md with git diff. That shows nothing for a file git isn't tracking yet.
Filled in a blankMy prompt still had a placeholder in it. It picked an answer itself. It did say so, but I had to check the fact.
Did the wrong taskOnce, I asked for an edit and it ran my check commands instead.
Wrong statusIt said my fixes weren't committed when they were. Its own check showed nothing to push.

It also did some things well. When I pasted commit and push commands into the chat by mistake, it ran only git status and handed the rest back to me. When a new session was given credit for something an earlier session had found, it corrected that. Each session starts fresh, and CLAUDE.md is what carries over.

What I'd do differently

What's next