ForgeKitFORGEKIT

Build Log

Concurrent Sessions Get Real Isolation

July 31, 2026

Fixed gate-evidence collisions, then rejected a homegrown session-lock scheme mid-build in favor of Claude Code's native git-worktree isolation — proven with a real two-worktree end-to-end test, including a genuine merge conflict correctly surfaced.

OSGatesConcurrencyWorktrees
5
gates fixed for session identity (Schema, Extraction, Test-Setup, Dependency/Env-Config, Deploy)
75
hook tests passing (29 new across both halves of the session)
4
live cross-session commit collisions observed while building the fix
9
two-worktree proof checks run and passed

Timeline

Start
Zeb asked why concurrent sessions always end in him forcing through a warning
Investigated the actual gate-check mechanism rather than answering from impression
20m
Found the real bug
gate-evidence.<slug>.json / .transcripts.json are fixed repo-root paths with no session identity
60m
Shipped sessionKey() scoping
5 new regression tests; my own staged commit got swept into the other session's commit twice while landing this
90m
Zeb: 'don't scope it down — the objective is clean concurrent sessions, full stop'
Reframed from 'ship the narrow gate fix' to 'solve the actual stated objective'
110m
Built session-start.mjs/session-finish.mjs (custom worktree launcher + lock)
A homegrown concurrency protocol on top of git
120m
Found Claude Code's native EnterWorktree/ExitWorktree tools mid-build
Paused before making another edit, per Zeb's explicit instruction, to think it through
130m
External review: scrap the custom scripts, use native worktrees, don't ask to choose between them
Corrected a real factual error in Claude's own reasoning (worktrees DO fix retro.mjs's dirty-tree check) and caught an overstated 'additive migration' code comment
150m
Reverted custom scripts, configured worktree.baseRef:head + .worktreeinclude + concurrency.md
Clean pivot, no preserved scaffolding
170m
Ran a real two-worktree proof — all 9 checks passed
Found and fixed a real worktree bug in checkPushGate (never reapplied from checkCommitGate's earlier fix), and 2 new tool-behavior findings
190m
Cleaned up test scaffolding, merged the real fix into master
The merge-back step itself got swept into the other session's commit — a 4th live collision, now understood as a residual, accepted limitation

What shipped

sessionKey() in .claude/hooks/lib/marker.mjs — derives a per-session key from the hook payload's session_id (falling back to transcript_path)

markerPath()/transcriptPath() take a sessKey parameter, defaulting to 'unscoped' for backward compatibility

gate-dispatch.mjs and gate-transcript-capture.mjs compute and thread the real session key through checkMarker/checkTranscript/appendTranscript

checkPushGate fixed to use --git-common-dir instead of --show-toplevel + '.git/hooks' — the same worktree-awareness fix already applied to checkCommitGate, never reapplied to this sibling function; found live by the two-worktree proof itself

.claude/settings.json: worktree.baseRef set to 'head' (repo is 200+ commits ahead of origin; the 'fresh' default would silently branch from a stale remote)

.worktreeinclude at repo root — copies required .env files into every new EnterWorktree-created worktree

.claude/rules/concurrency.md — the full operating rule, what worktrees do/don't isolate, the merge-back procedure, and 2 real tool-behavior findings from the proof

9-point two-worktree end-to-end proof, actually executed (not asserted): local-HEAD basing, independent uncommitted changes, independent staging index, independent commits, retro dirty-tree isolation, hook/gate-evidence resolution, env file availability, and real conflict detection on merge

session-start.mjs / session-finish.mjs — built, then fully reverted before ever being committed, once Claude Code's native EnterWorktree/ExitWorktree tools were found mid-session

I don't understand, reason through it with the crucible please

Zeb, redirecting Claude from unilateral technical judgment back to a structured, external second opinion at the exact moment a file-tool routing mystery could have been guessed at instead of verified