The pre-commit hook that exists because four lines made it to the live site

The plist in the macos-launchd-tcc article had a ProgramArguments entry pointing at the applet. Absolute path. Real username. Not a placeholder.

It rendered in a code block on the live site. Search engines indexed it. Anyone who read the article got the macOS username alongside the LaunchAgent configuration that used it.

The immediate post-mortem covered the discovery and the file edits that closed PR #17. This article covers what came after: a pre-commit hook that makes the same mistake harder to push.

What leaked

Two files. Four lines.

The first was in src/content/posts/macos-launchd-tcc.mdx. The article was a build log of the launchd and TCC fight: three days spent getting a scheduled job to read from an external SSD. The plist example needed a real path to be useful. I used my actual path. It rendered in the code block, live on the public site.

The second file was the template plist under scripts/launchd/. Three <string> values, each an absolute path with the username embedded. Those didn’t render anywhere. They were committed to a public repo, and git history is permanent.

The fix for the article was <your-username>. The fix for the template was __USERNAME__, a sed-friendly marker with a substitution step in the install script, so the actual username never needs to appear in the repo. Both fixes were twenty minutes of work. PR #17 closed.

The git history still has the four original lines. Rewriting history on a public repo trades one problem for another. It breaks any clone, any fork, any link to a specific commit. I accepted the tradeoff.

Why the file fix wasn’t enough

The immediate fix is behavioural. It relies on whoever is writing or editing to remember: examples use <your-username>, templates use __USERNAME__, absolute paths with real identifiers don’t go in public files.

That’s a reasonable convention. It’s not an enforced one.

I reviewed the article before it shipped. The plist was in a code block, which looks exactly right in an article about launchd configuration. I caught the TCC-grant sequence and the osacompile invocation. I missed the absolute path because absolute paths in code examples are correct syntax. The eye stops reading at the right pattern before it reaches the identifier.

That’s not carelessness. Under review load, attention locks onto the shape of a code block before it reads the identifier. Discipline addresses carelessness; constraint addresses attention.

The hook

The hook runs at commit time. It scans staged content for two patterns: the macOS username (case-insensitive, catches any capitalisation variant) and the full display name (exact case match). If either pattern appears in staged content, the commit is blocked. The error message names the triggering pattern and the file.

The hook lives at github.com/CaptainRandom00/git-hooks and installs globally via git config --global core.hooksPath ~/.git-hooks. One install covers every repo on the machine. An article draft in a different repo gets the same check.

This article is itself subject to the hook. It discusses the leak and the patterns the hook blocks without reproducing them. That’s why the sections above describe the patterns in plain language rather than in code.

The bypass

The bypass token is BBBRAIN_NAMELEAK_BYPASS=1, set as an inline prefix on the commit command:

BBBRAIN_NAMELEAK_BYPASS=1 git commit -m "chore: config with absolute path"

The token is deliberate friction. The hook isn’t intended to prevent every commit containing the username. There are legitimate cases; a private config repo with a real keychain path, for example. The token makes the bypass visible in shell history and slow enough to be intentional. You have to know it exists and type it out.

The pattern is consistent across the global hooks. Each hook in the repo follows the same shape: blocked by default, one env var unlocks it, the var name is BBBRAIN_<DOMAIN>_BYPASS. One pattern to learn, not one per hook.

The scope is narrow: staged content at commit time. Out of scope: history, unstaged changes, and pushes. A history scan on every commit would be intolerably slow, and unstaged content isn’t at risk until it’s staged.

What the constraint encodes

The hook is one string match. What it encodes is a decision: the macOS username is a personal identifier that doesn’t belong in public commits.

That sounds obvious after PR #16 and PR #17. Before those two PRs, the category “content that looks syntactically correct but is actually a privacy leak” didn’t have a slot in the review checklist. Absolute paths are correct syntax. They pass every lint rule. They look exactly like what they’re supposed to look like.

The hook names the category. If you’re committing the string, you know you’re doing it. The bypass exists so the answer can be yes-with-awareness rather than blocked-absolutely.

The same posture extends to the site architecture. Captain Random is a personal site; it doesn’t link to the software business, and the two aren’t named together anywhere on the site. They’re separate entities and the site treats them as such. That’s enforced by content decisions rather than a hook, because entity separation doesn’t reduce to a string match. The hook handles what can be made mechanical. The rest is convention.

Installing it

The hooks repo is public at github.com/CaptainRandom00/git-hooks. Clone and configure:

git clone https://github.com/CaptainRandom00/git-hooks.git ~/.git-hooks
git config --global core.hooksPath ~/.git-hooks

Every repo on the machine inherits the checks. If you maintain public repos from a machine where you also write config and build scripts, the macOS username is probably somewhere you didn’t intend to commit. The hook catches the simple cases: absolute paths in code examples and config templates that look syntactically correct right up until they don’t.

The four lines that shipped with PR #16 were that case. They were syntactically correct. They were also wrong.

All writing