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
| Piece | Choice | Why |
|---|---|---|
| Install | Native installer on my MacBook Pro | No Node.js needed, and it updates itself |
| Account | My Claude Pro plan | Not pay-per-use API billing |
| Instructions | CLAUDE.md in the repo | Claude Code reads it at the start of every session |
| First task | Audit pages, list findings, no changes | See what it finds before it edits anything |
| Commits | Me, signed with my SSH key | A 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:
- The site is plain HTML and CSS with no build step, and pushing to
mainredeploys it. - The Content Security Policy in
_headersblocks scripts, inline styles and outside resources. - The stylesheet version (
style.css?v=9) has to be bumped on every page when the CSS changes. - Adding a write-up touches four files. I checked this against an older commit and it was correct.
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:
- Never run
git commit,git push, or anything that changes git history. - For audits, list findings first and wait for approval before changing files.
- Don't change
_headerswithout asking first.
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:
- No broken internal links.
- No contrast failures, with ratios for each colour. I had the top values recalculated separately with the WCAG formula and they matched.
- A date that didn't match between an article and the lists that link to it.
- A duplicated block in
_headers. - A meta description that said "six things that broke" when the article lists five.
- Tables with no caption, and a footer arrow that screen readers read out loud.
- Heading levels on the home page and no skip links.
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
| Problem | What happened |
|---|---|
| Wrong review command | It 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 blank | My 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 task | Once, I asked for an edit and it ran my check commands instead. |
| Wrong status | It 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
- Set the permission mode and the deny list before the first session, not after.
- Fill in every blank in a prompt before sending it.
- Plan the audit around the usage limit from the start.
What's next
- Audit the rest of the pages, and fix the heading levels and skip links.
- Manage this domain's DNS records as code with Terraform, using Claude Code with the same guardrails.