Skip to content

SOPs

How We Name Anything

Anything that gets a name and persists past the moment it was written: repositories, scripts, launch agent labels and binaries, hooks, skills, markdown docs, folders, plist labels, doodle filenames. Variable names inside a single function are out of scope, that is a code-style call. This SOP is for names that get listed alphabetically somewhere and have to make sense to a future reader staring at them with no context.

For repository naming specifically, see Naming a New Repo. This SOP is the broader principle that repo-naming is one application of.

Every name must pass all three.

Read the name out loud. With no other context. Two years from now, can you tell what it does?

  • Pass: launch-tailscale-hidden.sh
  • Fail: open, script.sh, helper, final

If you might have five of these next year, does the name leave room?

  • Pass: liveview-ios (leaves room for liveview-chrome, liveview-mac, liveview-web)
  • Fail: script.sh (where do the next four go?)
  • Fail: crm (which CRM? there are now three of them)

The siblings test is the one that bites later. The repo for “the thing” becomes painful when “the thing” splits into four things and you have to retrofit a namespace into existing references.

Run the name through every tool that displays it: macOS Login Items, Finder, git log, launchctl list, GitHub repo list, your shell prompt. Strip away every parent folder, every breadcrumb, every visual hierarchy. What is left?

If what is left is Open or script or Final-Final, the name failed.

This test catches names that read fine in one tool but become useless in another. The Tailscale launch agent originally pointed at /usr/bin/open, and macOS System Settings showed the helper as just “Open.” The plist label com.user.tailscale-hidden would have read fine in launchctl list, but the binary leaked into the System Settings UI as the generic tool name.

In rough order of how often they bite.

open, run, script, helper, utils, misc, common. These are dumping grounds. The siblings test fails immediately. If you have a helper.sh, you will have a helper2.sh within a year and a helper-old.sh within two.

Pointing a launch agent or systemd unit at /usr/bin/open, /bin/bash -c '...', /usr/bin/python3 directly. The operating system displays the binary name, not your label. The display name becomes “Open” or “bash” or “python3.”

Fix: wrap the command in a named script with a descriptive filename. The OS shows the script name.

cc, tms, gkp, ath, mts. These are personal jargon. They mean nothing on their own. Pair them with a descriptive second half: cc-hooks, tms-internal, gkp-website, ath-upload-receiver. Never bare.

v2, 2026, new, final, final-final, actually-final. Versions rot. If the second version genuinely needs a different repo, rename the old one with -old or -archive. Do not bake the version into the name of the live thing.

2026-04-25.md, april-screenshot.png. The date is metadata, not content. Names need a subject. 2026-04-25-tailscale-rename.md works. 2026-04-25.md does not.

If the acronym is not expanded somewhere in the file or its parent README, do not use it. RCA is fine because it is documented. A new three-letter abbreviation that only you remember is not.

misc, stuff, temp, experimental, playground. These are signals that you did not decide what the thing is yet. Decide first, then name.

Lowercase hyphenated, subject + what it does. See Naming a New Repo for the full standard.

For multi-platform products, the subject stays and the platform varies:

  • liveview-ios
  • liveview-chrome
  • liveview-mac
  • liveview-web

Platform suffix is plain lowercase (ios not iOS). Sorts cleanly alphabetically.

verb-object[-modifier].(sh|py|js).

  • sync-claudemd-html.py
  • launch-tailscale-hidden.sh
  • verify-deploy.sh
  • post-push-sync.sh

Verb tells you what it does. Object tells you what it acts on. Modifier (optional) narrows the variant.

Reverse-DNS with a personal namespace: com.jameshurst.<subject>-<verb>.

  • Example: com.jameshurst.tailscale-launch-hidden

Sorts cleanly under jameshurst in launchctl list, separating personal agents from system and third-party agents. The label and the plist filename should match.

Always wrap generic binaries in a named script. Never point a launch agent directly at /usr/bin/open, /bin/bash -c, /usr/bin/python3, etc., unless you accept that macOS will show the System Settings UI as “Open” or “bash” or “python3.”

The wrapper script lives in ~/apps/cc/bin/ or wherever the project naturally puts its scripts, with a name that explains what it does.

Verb-noun.

  • create-blog-post
  • review-logs
  • email-journal

Verb first so they sort by action when listed.

Subject-first.

  • how-we-name-anything.md
  • repo-naming.md
  • working-over-ssh.md

Not naming-rules.md or rules-for-naming.md. Lead with the subject of the document.

Already codified in the super CLAUDE.md: <concept-slug>-v<n>-<provider>-<model>.png. Saved to ~/apps/doodles/<concept-slug>/.

Already codified: <type>_<topic>.md in ~/apps/cc/memory/.

Before:

  • Plist filename: com.user.tailscale-hidden.plist
  • Label: com.user.tailscale-hidden
  • ProgramArguments: /usr/bin/open -j -g /Applications/Tailscale.app
  • macOS Login Items showed it as: “Open”

The label was fine in launchctl list. The binary was wrong everywhere else.

After:

  • Plist filename: com.jameshurst.tailscale-launch-hidden.plist
  • Label: com.jameshurst.tailscale-launch-hidden
  • ProgramArguments: /Users/ojhurst/apps/cc/bin/launch-tailscale-hidden.sh
  • Wrapper script internally calls /usr/bin/open -j -g /Applications/Tailscale.app
  • macOS Login Items now shows: “launch-tailscale-hidden.sh”

Two changes: the label moved to a personal namespace, and the binary became a named wrapper script. Either change alone was insufficient. Both together make the system self-describing in every tool that displays it.

LiveView Multi-Platform (sibling structure)

Section titled “LiveView Multi-Platform (sibling structure)”

A product that exists as a Chrome extension, an iPhone app, possibly a Mac app, possibly a website. The natural early instinct is to name the first repo LiveView (one product, one repo). The siblings test catches it: there will be at least three repos.

The structure:

  • liveview-ios
  • liveview-chrome
  • liveview-mac
  • liveview-web

Subject (liveview) stays constant. Platform suffix differentiates. New platforms slot in without retrofitting a namespace.

If shared code emerges across platforms (a sync protocol, a data model), it gets its own repo: liveview-shared or liveview-protocol. The naming scales.

When you catch yourself thinking “I will name it properly later,” stop. Name it properly now.

Old code with a bad name is the worst kind of debt. By the time someone has the context to rename it, half the codebase references it, three cron jobs depend on the path, and a deploy script greps for the old string. The cost of renaming compounds. The cost of naming it right the first time is two minutes of thought.

The reflex: before you save, before you commit, before you gh repo create, run the three tests. If any fails, fix the name before the keystroke.

This SOP grows with experience. When a naming dilemma comes up that none of the existing patterns covers, add it as a worked example. The SOP earns its weight by accumulating real cases.