Name your Claude Code sessions after the client
Claude Code will happily run a session for every client you have, all at once. The catch shows up when you leave the desk. Open Remote Control on your phone, or claude.ai on an iPad, and the session list is a wall of titles like “Rebuild the sales model” and “fix the date table”. Which client was that? You end up opening three sessions to find the one you want.
The fix took an afternoon and we have not thought about it since. A hook reads a small file at the top of each client folder and titles the session on the first prompt, so the list now reads “ACME - Rebuild the sales model so finance and…”, “BRC - Publish the Claude post”, and so on. Every session is filed under its client before you have typed a second word.
How it works
Claude Code runs hooks at fixed moments. One of them, UserPromptSubmit, fires every time you send a prompt and can hand back a session title. Our hook does three things on the first prompt of a session:
- Walks up from the working directory looking for a
.client.jsonfile. - Reads a prefix from it, or builds one from the client name’s initials.
- Returns
<prefix> - <first prompt, trimmed to 40 characters>as the title.
It writes a marker file per session so it only ever runs once. Later prompts, and anything you set with /rename, are left alone. It is standard-library Python, so it runs the same on macOS and Windows.
Set it up
1. Put a file at the top of each client folder. Ours doubles as the config for time logging, so it carries a few more keys, but the hook only needs these two:
{ "client": "Acme Manufacturing", "session_prefix": "ACME" }
Leave session_prefix out and the hook uses the initials, “AM” in this case.
2. Save the hook as ~/.claude/hooks/session_title.py:
#!/usr/bin/env python3
"""Claude Code UserPromptSubmit hook: title the session after the client.
Looks upward from the working directory for a .client.json file. On the first
prompt of a session it sets the title to "<prefix> - <first prompt>". Later
prompts and /rename are left alone.
"""
import json, os, re, sys
from pathlib import Path
MAX_LEN = 40
STATE = Path.home() / ".claude" / "hooks" / "state"
def find_config(start):
for d in [start, *start.parents]:
f = d / ".client.json"
if f.is_file():
return json.loads(f.read_text(encoding="utf-8"))
return None
def prefix_for(cfg):
p = str(cfg.get("session_prefix") or "").strip()
if p:
return p
words = re.findall(r"[A-Za-z0-9]+", str(cfg.get("client") or ""))
return "".join(w[0] for w in words).upper()
def summarize(prompt):
text = " ".join(prompt.split())
if text.startswith("/"):
text = text[1:].split(" ", 1)[0]
if len(text) > MAX_LEN:
text = (text[:MAX_LEN].rsplit(" ", 1)[0] or text[:MAX_LEN]).rstrip(" ,.;:-") + "..."
return text
def main():
data = json.loads(sys.stdin.buffer.read().decode("utf-8-sig"))
session_id, prompt = str(data.get("session_id") or ""), str(data.get("prompt") or "")
cwd = Path(data.get("cwd") or os.getcwd())
marker = STATE / f"title-{session_id}"
if not session_id or not prompt.strip() or marker.exists():
return
cfg = find_config(cwd)
prefix = prefix_for(cfg) if cfg else ""
if not prefix:
return
STATE.mkdir(parents=True, exist_ok=True)
marker.write_text("", encoding="utf-8")
print(json.dumps({"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"sessionTitle": f"{prefix} - {summarize(prompt)}"}}))
if __name__ == "__main__":
try:
main()
except Exception:
pass
sys.exit(0)
3. Register it in ~/.claude/settings.json. If the file already has a hooks section, add the UserPromptSubmit entry to it:
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/session_title.py", "timeout": 10 } ] }
]
}
}
On Windows, replace python3 with python and use the full path to the script.
4. Start a session inside a client folder and send a prompt. The title changes as soon as the prompt goes through, and it is the title Remote Control and claude.ai show from then on.
Why the marker file matters
Without it, the hook would retitle the session on every prompt, and the title would drift to whatever you typed last. With it, the first prompt names the session and you can still rename by hand. That is the whole design: name it once, name it for the client, then get out of the way.
If you are running Claude Code across several clients and want help setting up the folder structure that makes this kind of thing easy, that is what our AI Starting Point session is for.