claude-siblings
v0.1.0
Published
Zero-infrastructure collision detector for concurrent Claude Code sessions — no daemon, no registry, sees sessions that never installed it
Maintainers
Readme
claude-siblings
Zero-infrastructure collision detector for concurrent Claude Code sessions.
claude-siblings tells you whether another Claude Code session is currently
working on your machine, before you write. It runs no daemon, no MCP server,
and no registry; it reads the process list and the mtimes of Claude Code's own
transcript files, so it detects sibling sessions that have never heard of
it. Existing coordination tools (claude-presence, claude-peers-mcp) need
every session to register or broadcast — which means the terminal you opened
before installing them stays invisible. One command, one exit code: 0 to
proceed, 2 to wait.
$ siblings check
BUSY — sibling activity detected:
transcript ~/.claude/projects/<slug>/bab1d011-….jsonl (age 148s)
transcript ~/.claude/projects/<slug>/59167d0d-….jsonl (age 32s)
$ echo $?
2Linux + Claude Code only. Tested against Claude Code CLI 2.1.228 (see Fragility — the mechanism reads undocumented internals).
Install
bunx claude-siblings check # no install
bun add -g claude-siblings # or global
bun build --compile src/cli.ts --outfile siblings # or a single binary from a cloneRequires Bun ≥ 1.0.
Commands
| Command | What it does |
|---|---|
| siblings check | One-shot scan. Exit 0 idle, 2 busy, evidence printed. |
| siblings wait [--timeout N] [--poll N] | Poll until all-idle (exit 0) or timeout (exit 124). |
| siblings doctor | What it found vs. expected: root, slugs, identity source, live claude count, config. |
Flags: --json (stable machine shape), --advisory (degrade instead of
failing when identity is unavailable), --from-hook (read session_id from
hook stdin JSON), --self <uuid>, --root <dir>, --window <min>,
--proc-patterns <a,b>.
Exit codes — the API
Hooks and scripts should branch on these, not on the text.
| Code | Meaning |
|---|---|
| 0 | IDLE — no sibling session or sibling heavy job detected |
| 2 | BUSY — sibling active; evidence printed |
| 3 | OVERLAP — reserved for v0.2 write-intent claims; never emitted in v0.1 |
| 4 | DETECTOR BLIND — transcripts root missing or holds nothing of the expected shape |
| 5 | IDENTITY UNAVAILABLE — cannot distinguish self from siblings (and --advisory not passed) |
| 124 | wait timed out |
Fail-loud is deliberate: a detector that cannot see (4) or cannot tell you
apart from a sibling (5) refuses to print IDLE, because a false "safe to
write" is worse than no tool at all. --advisory softens 5 into a scan that
prints identity unavailable — results advisory only and never returns 0 on a
non-empty scan.
How it works
Every Claude Code session exports CLAUDE_CODE_SESSION_ID into its Bash
subprocesses and writes everything it does under
~/.claude/projects/<slug>/ — a <uuid>.jsonl transcript plus a <uuid>/
state directory (subagents, tool results, workflows). So:
- Identity: resolve our own session UUID (
--selfflag → env var →--from-hookstdin), then drop every scanned path containing that UUID as a path component. That single rule self-excludes the root transcript, all subagent transcripts, and the very Bash call running this CLI. - Transcript scan: union of files under every project slug, newer than
the window (default 5 min), files only. Headless SDK helpers (the
"entrypoint":"sdk-cli"marker in the first 8 KB) are ignored. - Process limbs: exactly one live
claudeprocess that is provably ours ⇒ fresh mtimes are leftovers, not a live sibling. Heavy detached jobs (ffmpeg,yt-dlp, … — configurable) count only when their/procancestry reaches aclaudeprocess that is not ours, so a randomffmpegyou started by hand never reads as a sibling.
Any scan error defaults to BUSY. Full mechanism and rationale: docs/DETECTION.md.
What a clean check does NOT prove
A clean check proves a sibling stopped, not that your in-memory view of a file is current. If your editor or agent read a file ten minutes ago, re-read hot files before writing anyway — the sibling may have finished its write after your read.
Config
~/.config/claude-siblings/config.json (flags override the file):
{
"windowMin": 5,
"procPatterns": ["ffmpeg", "yt-dlp", "faster-whisper"]
}transcriptsRoot (default ~/.claude/projects) and sessionProcName
(default claude) are settable the same way.
The defaults were tuned against one person's multi-terminal cadence; if your
sessions idle differently, tune windowMin.
--json shape
{
"status": "busy",
"exitCode": 2,
"advisory": false,
"identity": { "uuid": "4697c9d2-…", "source": "env" },
"evidence": [ { "kind": "transcript", "path": "…", "ageSec": 32 } ],
"config": { "windowMin": 5, "transcriptsRoot": "…" },
"version": "0.1.0"
}Two-terminal proof
Terminal A runs a long tool call; terminal B runs siblings check and must
exit 2 naming A's transcript — and must not name its own. After A idles
past the window, B's siblings wait exits 0.
Recorded on the reference machine (2026-08-12, CLI 2.1.228, six live
sessions): check exited 2 naming three foreign session transcripts; the
caller's own UUID appeared zero times in the output. See
docs/VERIFICATION.md.
Fragility
This tool introspects undocumented internals: the transcript layout, the
JSONL header fields, the SDK entrypoint marker, and the
CLAUDE_CODE_SESSION_ID env var. Any Claude Code release can change them.
That is why every blind spot is a loud exit code instead of a quiet 0, why
siblings doctor shows found-vs-expected, and why the tested CLI version is
pinned above. Known limits: docs/LIMITS.md.
When NOT to use this
- If you can isolate, isolate. Git worktrees give real isolation; parallel-cc manages them for concurrent Claude Code sessions. This tool is for the shared-tree case.
- If you want durable locks or cross-machine presence, claude-presence does both (MCP + SQLite registry) — at the cost of only seeing sessions that registered.
License
MIT
