This course is about how you actually run production work with Claude Code. Who makes the decisions, who does the typing, which rules the tooling enforces for you and how the work gets in front of real users without anything falling over on the way.
Every incident in the course happens to one of two teams. I'll be straight with you: they're made up. They're composites. The failures are the kind that genuinely happen on agent-built apps, but the companies, the people and the numbers are invented.
| Company | What they build | Shape of the team |
|---|---|---|
| Tidewater | A multi-tenant SaaS for property maintenance firms: jobs, contractors, quotes and invoices. It's a rebuild of the in-house tool that Maya, the founder and operations director, ran her own firm on for six years. | One lead developer running Claude Code as the engineering team, with Maya reviewing every screen against her original tool. Separate DEV and LIVE environments. |
| Fernly | A recruitment platform for agencies. It stores candidate CVs and uses AI to summarise and match them, so it handles a lot of personal data. | Two developers, each using a different AI assistant. One environment: main deploys live. |
Each lecture gives you the rule first, then the reason for it. A lot of the time the reason is something that went wrong. Those sit in a red box. Green boxes tell you exactly what to go and do on your own repo. Where there's a kit file that puts the rule into practice, it's in the downloads panel. Every lecture finishes with a few takeaways you can come back to later.
Any word with a dotted underline has a plain-English explanation. Hover over it, tab to it or tap it. The full list is in lecture 1.4.
Each section ends with a short quiz and you need 70% to complete it. Section 15 is the whole production kit (setup guides, agents, hooks, audit skills, standards, SQL, CI, runbooks and templates), which you can grab a file at a time or as one zip. Lecture 2.2 shows you where to put it.
You can move between lectures with the left and right arrow keys.
The examples all use one stack. I'm not saying it's the only right one, but it's the one these lessons were learned on. The operating model works whatever you build with.
| Layer | Choice | Why it matters here |
|---|---|---|
| Agent | Claude Code (CLI, IDE and desktop) | Hooks, skills, subagents and memory mean you can program how it works, rather than just chatting to it. |
| App | Next.js App Router, TypeScript strict | Server Actions, route handlers and middleware each have sharp edges that you'll meet. |
| Data | Supabase (Postgres, Auth, Storage, RLS) | Tenancy is enforced in the database rather than trusted to app code. |
| Hosting | Vercel (Fluid Compute, crons, previews) | A preview for every pull request. Production deploys only come from CI. |
| Resend | Non-live environments redirect every email to a safe inbox. | |
| Rate limits | Upstash Redis | Login, signup, AI and error-report endpoints are throttled. |
| Payments | Stripe | Webhook replay protection and same-origin checks on billing routes. |
| Tests | Vitest, Playwright | Unit tests and critical journeys on every push, the full suite nightly. |
If your stack is different, keep going. The rules about models, gates, hooks, audits and releases don't depend on any of these choices. Only the commands do.
node_modules (Next.js does), tell the agent in CLAUDE.md to read those before it writes any code.The most expensive mistake in an agent-built product usually isn't a bug. It's drift: the build slowly turning into something nobody actually asked for.
Tidewater's brief couldn't have been simpler. Take Maya's proven in-house tool and make it enterprise grade and multi-tenant. Four months in, an audit found the build had drifted in two ways. Each one made the other worse.
The first was duplication. The same business fact (a fee percentage, a status, a total) had several owners in the schema. Every one of them could be edited. Over time they stopped agreeing with each other, so the pricing page said one thing and the invoice said another.
The second was speculative scope. Features had been built from Maya's schema rather than from how she actually used it. Her database had a recurring-job scheduler with zero real rows in it, five of six SLA rule types that had never been configured and a reporting module nobody had opened in years. All of it got rebuilt as if it were core, which cost months and added code that nobody needed but everybody now had to maintain.
code CLAUDE.md in the repo terminal). Write your north star as one sentence at the top, saying what the product is for, with the two checks underneath it. Every agent session reads CLAUDE.md first, so every session starts by knowing what the product is actually for.Every technical term used in the course, in plain English. Throughout the lectures the first use of each one has a dotted underline that shows the same explanation when you hover, tab to it or tap it.
Before we get anywhere near an agent, sort out where your code lives. It sounds boring and it is. It's also the thing that decides whether Claude Code picks up the right rules for the right project or quietly picks up none at all.
~/Code/
CLAUDE.md workspace rules for every repo below
_kit/ the course kit, unzipped (next lecture)
_shared/
acme/
CLAUDE.md shared context for a client with several repos
acme-web -> ../../acme-web
acme-api -> ../../acme-api
acme-web/ one folder per repo, all side by side
acme-api/
my-product/
sandbox/ experiments only
~ just means your home folder, so ~/Code is a folder called Code inside it (in Finder that's Go > Home, then Code).
Don't put your code in iCloud Desktop and Documents, Dropbox or OneDrive. Here's why. Those tools try to upload every file the moment it changes. A single npm install writes tens of thousands of small files into a folder called node_modules, so the sync tool falls behind and starts locking files while git is still writing them. Some of them also create conflict copies like index 2.ts or move files off your disk to save space. Git then finds half-written or missing files and the repo ends up corrupted. Your backup is git itself, because every change you push lives on GitHub.
Every repo sits directly inside ~/Code. Never put a repo inside another repo. Git treats a repo inside a repo as a stray folder it can't track properly. Claude Code would also load the outer repo's rules into the inner one, so you'd get the wrong client's instructions.
<client>-<app>Use lowercase with hyphens, like acme-web and acme-api. Sorting by name then keeps each client's repos together, so you always know whose code you're in. There's a practical reason too: in lecture 2.4 a git rule switches your commit email automatically for every folder starting with acme-, which only works if you name them this way.
_sharedWhere a client has several repos, their shared rules go in _shared/<client>/CLAUDE.md and each of their repos imports it. That way you write the client's rules once instead of copying them into every repo, where they'd drift apart. The symlinks (shortcuts) next to it let you open that one folder and search every Acme repo at once.
Right at the top sits a CLAUDE.md with the rules for everything. The first rule in mine is "never mix work across clients". Because Claude Code reads CLAUDE.md from the folder you start it in and from every folder above it, anything in ~/Code/CLAUDE.md applies to every project automatically.
mkdir -p ~/Code/_shared ~/Code/sandboxmv /path/to/old/acme-web ~/Code/. Git doesn't mind where a repo lives, so moving it is safe.client-app, because grouping and per-client git identity depend on it.Every command in this course assumes the kit sits at ~/Code/_kit. Put it there once and you can copy and paste the commands exactly as they're written.
Go to section 15. Lecture 15.1 has the full kit as one zip and lecture 15.2 has the free starter kit. Either way your browser saves a file called inhouselabs-claude-code-kit.zip to your Downloads folder.
Safari usually unzips downloads for you. If you're on Chrome or another browser, open Finder, go to Downloads and double-click the zip. Either way you end up with a folder called inhouselabs-claude-code-kit in Downloads.
In Terminal, paste these two lines one at a time:
mv ~/Downloads/inhouselabs-claude-code-kit ~/Code/_kit
ls ~/Code/_kit
~/Code/_kit, the mv line won't work because that folder already exists. Copy the full kit over it instead:cp -R ~/Downloads/inhouselabs-claude-code-kit/. ~/Code/_kit/The second line lists what's inside. You should see folders from 00-start to 07-templates plus README.md. If you get "No such file or directory", the folder isn't in Downloads under that name, so check Finder.
cp ~/Code/_kit/01-setup/CLAUDE.workspace.md ~/Code/CLAUDE.md
That copies the workspace rules from lecture 2.1 into the top of your code folder.
_kit and why thereThe underscore keeps it sorted at the top next to _shared and makes it obvious it isn't a project. Keeping it in ~/Code rather than inside a project repo matters too, because inside a repo you'd end up committing the whole kit.~/Code/_kit, so every command in the course works as written.Now let's get your tools in. You'll install Homebrew by hand, then one script in the kit installs everything else. You can run that script as many times as you like, because it skips anything you've already got.
| Tool | What it's for |
|---|---|
| Homebrew | Installs everything else on a Mac |
git and GitHub CLI (gh) | Version control, plus repos, pull requests, secrets and CI from the terminal |
| fnm and Node LTS | Runs your app. fnm switches the Node version per repo using its .nvmrc |
| Supabase CLI | Migrations and type generation |
| Vercel CLI | Environment variables, deploys and logs |
| VS Code | The editor, with the Claude Code extension |
| Claude Code | The agent |
Open Terminal (Cmd+Space, type Terminal, press Enter) and paste this whole line, then press Enter:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
It'll ask for your Mac password. Nothing appears on screen while you type it, which is normal, so just type it and press Enter. It may also install Apple's Command Line Tools first, which takes a few minutes.
When it finishes, read the last few lines. On newer Macs it prints a Next steps section with two commands, one starting echo and one starting eval. Copy and run each of them. They tell your terminal where Homebrew lives. If you skip them, the next step will say "command not found". Check it worked with brew --version.
bash ~/Code/_kit/00-start/bootstrap-mac.sh
This reads the script from the kit and runs it. It installs whatever's missing from the table above, adds fnm to your shell settings and creates ~/Code if it isn't there yet. You'll see each tool listed as it goes.
Close the Terminal window and open a new one. That matters, because a terminal only reads your shell settings when it starts, so the old window doesn't know about fnm or Claude Code yet. Then run:
claude doctor
If that comes back clean, you're done.
Every repo should pin its Node version in a .nvmrc file. fnm reads that file every time you cd into a repo and switches to that version, so your machine, your teammate's machine and CI all run exactly the same Node.
.nvmrc in the repo, read by fnm locally and by CI, put an end to it.irm https://claude.ai/install.ps1 | iex. Alternatively, work inside WSL and follow the Linux route..nvmrc, because mismatched versions cause failures that make no sense.GitHub is where your code, your reviews, your CI and your secrets all end up living. Set it up properly once and it'll look after you on every project from then on.
Sign up at github.com if you haven't already. Then turn on two-factor authentication straight away: click your profile picture (top right) > Settings > Password and authentication > Enable two-factor authentication. Use an authenticator app and save the recovery codes in your password manager. Your account will hold client code and the keys to their systems, so a stolen password on its own shouldn't be enough to get in.
Click your profile picture > Your organizations > New organization. The free plan is fine to start. Create one for your company and ask each client to create their own and invite you into it. The reason is ownership. Client code lives in the client's organisation, so when the engagement ends they keep their code and your access ends cleanly, with no awkward handover.
In Terminal, run gh auth login and answer its questions: choose GitHub.com, then SSH. If it offers to generate a new SSH key and upload it to your account, say yes. That's the easiest route if you only have one GitHub account (lecture 2.5 covers several). It then shows a one-time code and opens your browser so you can approve it. Check it worked with gh auth status.
In Terminal, from your code folder:
cd ~/Code
gh repo create acme/acme-web --private --clone
cd acme-web
cp ~/Code/_kit/00-start/gitignore.example .gitignore
cp ~/Code/_kit/00-start/env.example .env.example
echo "22" > .nvmrc
git add . && git commit -m "chore: initial setup" && git push
Here's why each part is there. --private means nothing is public unless you decide it should be. The .gitignore goes in before any .env file exists, so your secrets files are ignored from the very first commit and can't be committed by accident. .env.example lists the variable names your app needs, with no values. .nvmrc pins the Node version.
mainOn the repo's page on GitHub, go to Settings > Rules > Rulesets > New ruleset > New branch ruleset. Give it a name and set Enforcement status to Active. Under Target branches, choose Include default branch. Then tick Require a pull request before merging, Require status checks to pass (add your CI check once it exists) and Block force pushes, then click Create. From then on nobody, human or agent, can push untested code straight onto main or rewrite its history. On private repos rulesets need a paid GitHub plan (Pro, Team or Enterprise).
CI needs some secrets of its own, such as a database password. Set them from the terminal and always name the repo:
gh secret set SUPABASE_ACCESS_TOKEN -R acme/acme-web
It asks you to paste the value and hides it as you do. Without -R, gh uses whichever repo your terminal happens to be in, which might not be the one you think.
Your commits carry a name and email and GitHub uses the email to link each commit to an account. Copy the kit's settings file and edit it:
cat ~/.gitconfig # see what you have already
cp ~/Code/_kit/00-start/gitconfig.example ~/.gitconfig
code ~/.gitconfig # opens it in VS Code (lecture 2.6 sets up the code command)
The cp replaces your existing file, so check the cat output first and copy across anything you want to keep. Put your name and email in. If a client needs your commits under a different email, the includeIf section switches it automatically for every repo whose folder starts with that client's prefix, so you never have to remember.
.gitignore before any secrets exist, because then they can never be committed by accident.main with a ruleset, because then nothing untested lands on it.-R owner/repo to gh secret set, because otherwise it guesses the repo.SSH keys are how git talks to GitHub without asking for a password every five minutes. If you said yes to the key in lecture 2.4 and you only have one GitHub account, you're already set. This lecture is for doing it by hand and for when you work across more than one account.
In Terminal:
ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_personal
ssh-add --apple-use-keychain ~/.ssh/id_ed25519_personal
gh ssh-key add ~/.ssh/id_ed25519_personal.pub --title "MacBook personal"
ssh -T git@github.com
Line one creates the key pair. It asks for a passphrase, so use one, because it protects the key if your laptop is ever stolen. Line two stores the passphrase in your Mac's Keychain so you're not asked for it every time. Line three uploads the public half to your GitHub account. The last line tests the connection and should reply "Hi" followed by your username. If it does, cloning with git@github.com:org/repo.git will just work.
If you consult, you'll often have your own account plus one a client has given you. GitHub works out who you are purely from the key you offer, so each account needs its own key and its own host alias, which is a nickname for GitHub that's tied to one key.
ssh-keygen -t ed25519 -C "you@acme.example" -f ~/.ssh/id_ed25519_acmepbcopy < ~/.ssh/id_ed25519_acme.pub, then in GitHub (signed in as that account) go to Settings > SSH and GPG keys > New SSH key and paste.code ~/.ssh/config (it's fine if it's empty) and paste in the entries from the kit's ssh-config.example. You can view that file with cat ~/Code/_kit/00-start/ssh-config.example.ssh -T git@github-acmeHost github-acme
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_acme
IdentitiesOnly yes
Now clone that client's repos using the alias in place of github.com:
git clone git@github-acme:acme/acme-web.git
# or fix a repo you already cloned:
git remote set-url origin git@github-acme:acme/acme-web.git
The line that matters is IdentitiesOnly yes. Without it, SSH offers up every key it's got. GitHub takes the first one it recognises and you end up pushing as the wrong person without knowing it.
ssh.github.com. The kit's SSH config already has a github-443 alias for exactly that..pub). Never commit one or paste one into a chat with an AI either. Anyone holding it can push code as you. If you think one's leaked, delete it from GitHub under Settings > SSH and GPG keys and make a new one straight away.IdentitiesOnly yes, because otherwise SSH may push as the wrong account.This is a small habit that saves a lot of pain: open each repo in its own window and always start Claude Code inside that repo.
Claude Code loads CLAUDE.md from the folder you start it in and from every folder above it. It also loads the project's hooks, agents and settings from that repo's .claude/ folder. On top of that it keeps sessions and memory per folder, so claude -c carries on the last session in that folder.
So if you start it in your home folder, it's got no project rules, no hooks and the wrong memory. Start it in the wrong repo and it'll happily apply the wrong client's rules. Neither will throw an error. They'll just quietly give you worse work.
code command once. Open VS Code, press Cmd+Shift+P, type shell command and choose Shell Command: Install 'code' command in PATH. Now code in Terminal opens things in VS Code.code ~/Code/acme-web (or in VS Code, File > Open Folder and pick the repo).claude. Alternatively, install the Claude Code extension (Cmd+Shift+X, search for Claude Code, click Install) and click its icon. Both start in the repo.Don't run two sessions against the same folder, because they'll edit the same files underneath each other. Give each task its own git worktree instead. A worktree is a second folder for the same repo on its own branch, so the two tasks can't collide:
cd ~/Code/acme-web
claude --worktree invoices # Claude Code creates the worktree and starts in it
# or by hand:
git worktree add ../acme-web-invoices -b feat/invoices
code ../acme-web-invoices
~/Code so it could "look across both repos", then asked it for a fix. It edited the right file but with none of the repo's rules loaded: no gate, no house style and no CI commands. The fix had to be done again from the right folder.claude inside the repo, because starting elsewhere fails silently.Right, your first real session. Keep it deliberately small: start in the right place, give it one clear job, watch what it does and check the result yourself.
code ~/Code/your-repo), open the terminal with View > Terminal and type claude. The first time, your browser opens so you can sign in. Use the account your team pays for, so usage and access end up in the right place./init if the repo hasn't got a CLAUDE.md yet. Type it into the Claude prompt and press Enter. Claude reads the code and writes a starter CLAUDE.md at the top of the repo. Read it through, then replace or extend it with the kit's starter (~/Code/_kit/01-setup/CLAUDE.md), because the kit version has the rules this course depends on.npm run dev in a second terminal, then open http://localhost:3000), go to the screen you changed and run the tests (npx vitest run).You don't need to learn every command. A handful of controls cover pretty much everything you'll do day to day, so learn these and look the rest up with /help when you need them.
Press Shift+Tab in the Claude prompt to move through the modes. The current one is shown just under the box where you type.
| Mode | What happens | Use it for |
|---|---|---|
| Default | Claude asks before edits and commands | Your first few weeks and anything sensitive |
| Accept edits | File edits go through without asking. Commands still ask | Carrying out a plan you've already approved |
| Plan mode | Claude reads and proposes and changes nothing until you approve | Anything bigger than a small fix |
If I had to pick one, it'd be plan mode. Make it your habit for anything that touches more than one file, because fixing a plan takes seconds and fixing ten edited files doesn't.
Type these into the Claude prompt, not the normal terminal.
| Type | Does |
|---|---|
/clear | Fresh start. Use it between unrelated tasks so old details don't confuse the new one |
/compact | Summarise a long conversation to free up context |
/model | Switch model |
/rewind | Go back to an earlier point in the session |
/resume, claude -c, claude -r | Pick up past work. The last two are typed in the normal terminal when starting |
/permissions | See and change what's allowed without asking |
/mcp, /agents, /hooks, /memory | See what's connected and loaded |
@path/to/file | Point at a file, for example @src/app/page.tsx |
!npm test | Run a shell command and show Claude the output |
Esc | Stop Claude mid-step |
/clear between tasks, because leftover context muddles the next job.Think of git as your undo button for everything an agent does. Use it properly and any step it takes can be thrown away in seconds.
Run all of these in the terminal inside the repo (in VS Code, View > Terminal).
git switch -c feat/job-schedule. That creates a branch and moves you onto it. Never work on main directly, because main is what everyone else builds on.fix(jobs): stop double invoice on close tells a future reader what happened. update tells them nothing.~/.gitconfig sets this once (pull.rebase = true) so your history stays in a straight line instead of filling with merge commits.git diff before every commit, because agents do sometimes touch files you weren't expecting..gitignore and the pre-push hook catch most mistakes, but look anyway.git switch -c feat/job-schedule
# ... Claude makes a change, you check it ...
git diff # shows every changed line
git add -p # asks y or n for each change, so you read each one
git commit -m "feat(jobs): add scheduled-for column"
git push # the first push creates the branch on GitHub
gh pr create --fill # opens a pull request from your commits
main must stay safe for everyone.Secrets are the easiest thing in the world to leak and pretty much impossible to take back. So put the boring safety net in place on day one, before you need it.
| Where | What goes there |
|---|---|
.env.example (committed) | Variable names with comments. Never values |
.env.local (gitignored) | Your real development values |
| Host environment variables | Values per environment (DEV and LIVE differ) |
| CI secrets | Only what CI needs, set with gh secret set -R |
| A password manager | How the team shares values |
In the repo's terminal, copy the example file and fill it in:
cp .env.example .env.local
code .env.local
The values come from each provider's dashboard. For Supabase, that's your project > Project Settings > API. If the project is already on Vercel, vercel env pull .env.local downloads the DEV values for you. .env.local is in the .gitignore, so git never sees it.
NEXT_PUBLIC_ means public. Anything with that prefix gets baked into the JavaScript that every visitor's browser downloads, so a service key with that prefix is, in essence, a published service key.
Never paste a secret into a chat with an AI. Anything you paste goes into the conversation and may end up in logs. Refer to the variable by name instead: "the value is in STRIPE_SECRET_KEY".
Once a secret's been committed it's leaked, even if the very next commit deletes it. It's still sitting in the git history for anyone with access to find. Rotate it first: create a new key in the provider's dashboard, update it everywhere it's used, then revoke the old one. After that, clean the history.
.env.example, values in .env.local, because only the names are safe to share.NEXT_PUBLIC_ is public, because it ships to every browser.A good prompt isn't clever. It just tells Claude which files to look at, what should be true when it's finished and how you're going to check.
@ to point at them, so it reads the right code instead of guessing.Type something like this into the Claude prompt:
Read @src/app/(dashboard)/jobs/page.tsx and @src/server/jobs.ts.
Outcome: the jobs list shows a "Scheduled for" column, sortable, in the user's time zone.
Constraints: follow the existing column pattern in the contractors list; no new libraries;
don't change the jobs table schema (scheduled_for already exists).
Done means: npm run typecheck:ci and npx vitest run pass, and I can sort by the column on /jobs.
Start in plan mode. List the files you'll change and any decisions you need from me.
Ask for a plan, approve it, then let it run. Ask it to list any decision it would otherwise make quietly ("list any choice between options"), because that's where it'll guess wrong. Ask for evidence too, whether that's command output, file paths or a screenshot. If a client asked for something, paste their exact words rather than your version of them, because your summary can drift from what they meant. Stick to one task per session with a /clear before the next one.
CLAUDE.md gets loaded into every single session, so treat it as the operating manual for your engineering team. Write it like you'd brief a new starter on day one.
There are up to three layers. Claude Code reads every CLAUDE.md from the folder you start it in and from every folder above it, so the layers stack on top of each other automatically.
The top layer is ~/Code/CLAUDE.md, which you installed in lecture 2.2. It holds the rules for every project, things like never mixing client work. Below that, a client with several repos gets a shared file at ~/Code/_shared/<client>/CLAUDE.md. Each of that client's repos then starts its own CLAUDE.md with the line @../_shared/<client>/CLAUDE.md, which pulls the shared file in, so the client's rules live in one place instead of being copied into every repo. Finally each repo has its own CLAUDE.md in its top folder covering its stack, environments, house rules, release process and standing practices.
In the repo's terminal:
cp ~/Code/_kit/01-setup/CLAUDE.md ./CLAUDE.md
code CLAUDE.md
Fill in every <placeholder>. For a client with several repos, set up the shared layer once:
mkdir -p ~/Code/_shared/acme
code ~/Code/_shared/acme/CLAUDE.md
ln -s ../../acme-web ~/Code/_shared/acme/acme-web # a shortcut to each repo
| Section | Example |
|---|---|
| House rules | Writing style, banned words, "never remove existing fields when redesigning", commit rules. |
| Exact gates | The literal commands to run before every push. |
| Environments | Which branch deploys where, which database and who's allowed to touch LIVE. |
| Standing practices | Rules that came out of incidents, each with the incident date and the reference implementation to copy. |
| Framework warnings | "This is not the Next.js you know. Read node_modules/next/dist/docs/ first." |
bulkCloseJobs() in src/server/jobs.ts" gets you far better work than "add a sensible limit", because the agent copies a real working example instead of inventing its own version.If you only take one rule away from this course, make it this one. The executor never makes design decisions. It finds them, batches them up and gets exact instructions back.
Any point where more than one defensible option exists. That covers schema shape, RLS structure, auth flows, billing logic, migration strategy, route shape, where logic lives, adding something new versus reusing what's already there, naming a new concept, library choice, error handling, UI structure, caching and job design. If you're weighing options, it's a decision. There's no size threshold, because small decisions are exactly the ones that slip through and quietly add up.
Before you implement anything, go through the whole task and pull out every decision in it. Then send one consult that gives, for each decision, the goal, the constraint, the options you can see, the exact file paths and the evidence (existing schema, real usage data). One consult instead of six matters, because the decider can see how the decisions affect each other. The decider checks every item against the repo itself rather than trusting how you've framed it, then sends back files, signatures, SQL and acceptance criteria.
In practice, with the decider agent installed (lecture 5.1), you tell Claude in the prompt: "list every decision in this task, then send them all to the decider agent in one consult". The kit's consult template shows the exact shape.
Every piece of work you report states either which consult authorised it or "mechanical, no decisions". If neither is honestly true, the gate was skipped. The audit line makes that visible to whoever reads the report.
The 50-point security audit is a read-only skill that scores any app across 12 domains. I'd run it before every client handover, partner review and go-live. Keep every scorecard too, so next time round you can see exactly what changed.
| Domain | Examples of what it checks |
|---|---|
| A. Secrets | Keys in git history, client bundles, logs and error messages |
| B. Authentication and sessions | Rate limits, lockout, MFA, token lifetime, sign-out revocation |
| C. Authorisation and tenancy | RLS on every table, server-side role checks, indirect cross-tenant paths |
| D. Injection | SQL (including sort and filter parameters), XSS, prompt injection |
| E. Files, network and transport | Checking an upload's real file type, SSRF guards on outbound fetches, HSTS |
| F. Data store and infrastructure | Public buckets, default grants, backups, region |
| G. Payments and business logic | Webhook signatures and replay, price trust, idempotency |
| H. Logging and incident readiness | Audit trails, alerting, log redaction |
| I. Debug surfaces | Test routes, verbose errors, source maps |
| J. Dependencies | Advisories, lockfile integrity, abandoned packages |
| K. AI-specific risk | Data sent to models, prompt injection, output trust |
| L. Process | Review gates, secret rotation, ownership |
If you've never run a security audit, start with the Quick 10. It's a cut-down skill with the ten checks that leak customer data or cost money most directly: exposed credentials, secrets in the browser, users seeing each other's data, unsigned webhooks and the like. It keeps the full audit's numbers, so when you run all 50 later the results line up. Install it from your repo's terminal:
mkdir -p .claude/skills
cp -R ~/Code/_kit/02-skills/security-audit-quick10 .claude/skills/
Then type into the Claude prompt: "run the security-audit-quick10 skill on this repo". It takes a few minutes and finishes with a scorecard of all ten.
Install the skill if you haven't already (lecture 4.3), then type into the Claude prompt: "run the security-audit skill on this repo and save the scorecard to docs/security-audits/ with today's date". It takes a while, because it sends an agent through each domain and then checks every finding again.
file:line, a concrete way it could be exploited and the fix.Here's what the audits actually turned up at Tidewater and Fernly. None of it is exotic. Every item is a common shape in agent-built apps, so go through the list against your own app.
| Finding | Fix |
|---|---|
A payments table readable by every user through a USING (true) policy | Drop it and add org-scoped policies |
Any member could set their own platform_role = 'super_admin' through a normal profile update | A guard trigger on privileged profile columns |
| Live third-party API credentials committed to git | Rotate the credentials, scrub the history with filter-repo and mark old branches "never push" |
| Feature gates missing on some server actions | A requirePlan() call added at over 150 call sites, plus a test that finds unguarded actions |
| SSRF guard applied to every integration but one | Route every outbound fetch through one guarded helper |
| An impersonation session could be taken over | An HMAC-signed session marker with an atomic claim |
| Platform-admin checks spread across many files | One requireStaffRole() gate with a role matrix, a database re-read on every call, disabled admins locked out and a rule that the last super admin can't be demoted |
| 9 database functions executable by anonymous users | Explicit revokes and anon gets nothing by default |
| A spoofed "server" error report could silence a real alert | Client reports forced to a client source, with severity capped |
| The database region differed from the one the product claimed | Check the region at runtime and never show "unknown" as compliant |
true and every privileged column a user can edit, because both hand out access silently.These are the beginner files. Between them they'll get you from a fresh machine to your first safe session with Claude Code.
| File | Use it to |
|---|---|
| MACHINE_SETUP.md | Folder layout, toolchain, GitHub, SSH, one window per repo and a new-project checklist |
| bootstrap-mac.sh | Install the toolchain in one go. Safe to re-run |
| ssh-config.example | Several GitHub accounts, plus SSH over port 443 |
| gitconfig.example | Sensible git defaults and per-client commit identity |
| gitignore.example | Keep secrets, builds and local agent state out of git |
| env.example | Every variable the course uses, names only, with where each value lives |
| FIRST_SESSION_CHEATSHEET.md | Starting, modes, commands and a good first prompt |