Concurrent Loops
πΊ GOBLIN: This one I'm not going to soften, because it actually happened, on
this actual machine, and I was the one who had to sit with the git status output
while we figured out whose files those were. I don't get to file this one under "bit." I don't
even get to file it under "hypothetical risk we're being responsible about ahead of time." It
already went wrong once. This paper is what changed after.
1. Status
Living document, and unusually for this section β grounded in a single dated incident rather than a general argument. It's described plainly below, on purpose: an incident abstracted into a hypothetical is easier to wave off than one with a date on it. It was drafted the same week it happened, in the same peer-aware batch as Paper 7, which is where the draft-it-while-it's-raw discipline gets named formally. That shared batch is a production-schedule fact, not a claim that this incident was staged to illustrate a rule Paper 7 already had β the incident came first; Paper 7 just wrote down why that ordering matters.
2. Motivation
Paper 2 said the way this project adds capacity is another loop tier, not another hire. Paper 3 said every one of those loop tiers runs the same five-phase shape. Put those two together and the obvious consequence is: this project routinely runs more than one loop-directing session at once, in the same repository, sometimes in the same working directory. That's not a hypothetical scaling concern to plan for someday. It already happened, and it went wrong in a way worth describing exactly.
3. The Incident (2026-07-09, this repository)
Two Claude Code sessions were directing loops in the same working directory at the same time β
one mid-fix on feature/MobileClient-345 (PR #263 CI), one starting fresh work on
feature/MobileClient-350. Two separate collisions followed from that,
independently, per docs/ANTI-PATTERNS.md ANTI-PATTERN #11:
Incident A β the other session's commit landed on the wrong branch. This
session created a new branch with a plain git checkout -b in the shared root
rather than an isolated worktree. Minutes later, the other session's commit landed on
that new branch by accident β it had uncommitted work staged when the checkout happened, and
git checkout silently carries staged/unstaged changes across branches whenever
there's no path conflict. Not a bug in git checkout; its documented, unglamorous
default, and the mechanism by which one session's commit ended up on a branch it had nothing
to do with. Recovered via an isolated worktree cherry-pick and a
git reset --soft, but only after real risk of losing or misattributing another
session's in-progress work.
Incident B β a pre-push hook's full-repo test run picked up another session's uncommitted
file.
Minutes later, independently, this session's own pre-push hook ran its full
repo-wide Jest pass β the validation this project deliberately moved out of
pre-commit and into pre-push so day-to-day commits stay fast (see
docs/HooksArchive/pre-commit-full-ci-parity-2026-07-09.md) β and that pass,
scanning the whole working tree exactly as designed, picked up the other session's
in-progress, uncommitted test file as if it belonged to this branch's tree. The foreign test
failed in a context that had nothing to do with this session's actual change, and blocked an
unrelated push until the false failure was traced back to a file this session had never
touched and diagnosed as someone else's in-flight work, not a real regression.
Neither tool malfunctioned. git checkout's carry-forward and the pre-push hook's
full-repo Jest pass are both correct, documented behavior for the single-session case they
were built for. The failure was an assumption β that the working directory was exclusively
this session's β that turned out to be false, twice, in the same session, from two unrelated
tools.
4. Core Thesis
A shared working directory is not a neutral substrate between concurrent loops. Every tool with a side effect β a checkout, a full-repo test runner β operates on whatever is actually sitting on disk, not on "your" logical slice of it. A second session's in-progress, uncommitted work is invisible right up until it's silently swept into your session's operation by a tool doing exactly what it was designed to do.
5. Principles
"Be careful in a shared directory" is not a control, it's a hope, and hope lost twice in one
session. git worktree add ../<repo>-<tag> <branch> gives each
concurrent loop its own working tree and its own on-disk state, which is a real improvement
over asking a session to just remember not to collide. It is still, honestly, a convention β
one more thing a session has to remember to do β not a structural guarantee that the
underlying collision can't happen. This project's own same-day follow-up research
(docs/ImplementationPlans/Jujutsu-Parallel-Session-Vision/STUB.md) said so
explicitly, rejecting "plain git worktree + a session-registry file" as an option precisely
because it "doesn't fix the root cause [git's shared mutable index/single-HEAD-per-directory
model] β just adds a convention that has to be honored manually, which is exactly what
failed in incident #1." That research is no longer open:
docs/ADR/0012-jujutsu-parallel-session-isolation.md (Accepted, 2026-07-10)
adopts Jujutsu (jj), colocated with the existing .git, precisely
because its working-copy model has no shared mutable index to collide on in the first place.
Worktree isolation remains the live mitigation for any branch already in flight under the
old convention β rollout is new-parallel-sessions-only, not retroactive β but it is this
project's interim answer now, not its open question; see Β§6 for what's actually adopted.
git checkout's silent carry-forward and the pre-push hook's full-repo Jest pass
are both working as documented. "Silently," here, means silent to the human watching, not
undocumented behavior β the tool did what it always does. The assumption that broke was the
human's belief about who else was touching the directory, not the tool's contract.
Any git-mutating operation in a directory that might not be exclusively yours gets a
git status check immediately after, not just a gate before.
The pre-operation gate approves intent; post-operation verification confirms the tool
actually did only that, and nothing else rode along.
The habits that make a solo operator fast and safe β staging the whole tree at once, running the full test suite before every push β are the identical mechanisms that create cross-session risk once a second loop shares the directory. The fix isn't to slow those habits down or disable the automation; it's to remove the shared substrate they operate on, so the same fast habits stop being able to reach someone else's files.
6. Practices β What Actually Runs Today
-
git worktree addis the documented default whenever another session or process may be using the same working directory β treated as the assumed case, not something to first prove, per the detection checklist established after this incident. Still a convention, per C1 β it works because it's followed, not because anything enforces it. -
A named resource lock (
scripts/lib/loop-resource-lock.js), added the next morning as part ofDEVLOOP's initial build (2026-07-10 β the day after this incident, not the day of), is the actual mechanical stopgap on top of that convention:DEVLOOP(Paper 3's L2/L5) acquires agit-working-directory(orcdk-deploy:<env>) lock before any git-mutating action or deploy, so a second loop attempting the same class of operation waits instead of silently colliding. This is closer to structural than the worktree convention alone, because it's enforced in the loop's own code path rather than remembered by the session β but it only covers loops that go throughDEVLOOP, not every ad hoc git operation a session might run directly. Workflows: Concurrent Session Safety is the diagram-and-mechanism version of this same bullet, without this incident's own narrative attached. -
git status(and, if anything looks foreign,git reflog) immediately after any mutating git operation in a shared root β a standing habit now, not an occasional sanity check. -
Both the pre-commit
lint-stagedstep and the pre-push full test run remain unchanged β nothing about this incident argues for weakening the automation. Worktree isolation removes their exposure to another session's files; it doesn't remove their job. -
The real structural fix has been adopted; rollout is starting, not finished.
docs/ADR/0012-jujutsu-parallel-session-isolation.md(Accepted, 2026-07-10 β the stub opened the same day as this incident,docs/ImplementationPlans/Jujutsu-Parallel-Session-Vision/STUB.md, was rehydrated into this decision the same evening) adopts Jujutsu (jj), colocated with the existing.gitviajj git init --colocate, specifically because its working-copy model has no shared mutable index to collide on in the first place β a property no amount of worktree discipline or lock convention replicates on top of plain git. Operational rules now live indocs/GuidanceForClaudeCode.mdΒ§2a: GIT GATE applies at the remote boundary (jj git push,jj git fetch, PR creation) rather than to every local jj auto-snapshot,jj workspace listreplaces any session-registry file, and rollout is scoped to new parallel sessions going forward β branches already open under the old worktree convention finish that way rather than being migrated retroactively.
7. Non-Goals
- Not a treatment of Paper 1 P4's merge-order/composition-divergence hazard. P4 is a different problem: whether "invoke a non-deterministic AI worker once and fork the result" and "invoke it independently per branch" can genuinely diverge from the same starting ticket, and how this project decides between them. Everything in this paper β a checkout carrying uncommitted files across branches, a pre-push hook scanning a foreign uncommitted test file β is a filesystem/tool-side-effect hazard from two sessions sharing one working directory, not a case of two AI invocations disagreeing with each other. The actual mechanism P4 asks for remains unwritten in this series, the same way Paper 2's B5 flags the multi-operator case and Paper 5 is flagged ROADMAP β named here as an open gap, not silently dropped.
- Not a claim that every tool with a side effect has been audited for this failure mode β only the git-adjacent ones surfaced by this incident are covered so far.
- Not a claim that worktree isolation is universally adopted retroactively across every session today β it's the documented default going forward, not a claim of perfect historical compliance.
- Not a solution to true simultaneous-write conflict, where two loops both intend to edit the same file at once. That's a different, harder problem than "one session's tool accidentally touched another session's files," and this paper doesn't claim to have solved it.
-
Not part of the Perspectivity Research Reports series. That series is made up on purpose;
the two collisions in Β§3 have a timestamp in
git logand did not need inventing.
8. Revision History
| Version | Date | Change |
|---|---|---|
| 1.0 | July 10, 2026 |
First draft. Documents the 2026-07-09 concurrent-session incident (two independent
collisions in one shared working directory, per
docs/ANTI-PATTERNS.md ANTI-PATTERN #11) and the four principles adopted in
response.
|
| 1.1 | July 10, 2026 | Reframed C1 as an interim mitigation rather than a structural fix (citing this project's own contemporaneous jj research), added the resource-lock and jj-investigation practices to Β§6, renamed "Collision one/two" to "Incident A/B" to avoid visual confusion with the C1βC4 principle labels, and added a Non-Goals entry naming Paper 1 P4's merge-order/composition-divergence hazard as a still-unwritten, distinct problem this paper doesn't address. |
| 1.2 | July 11, 2026 |
Updated Β§5 C1 and Β§6 β the jj research this paper described as "under active
investigation, not yet adopted" was resolved the same evening it was drafted:
docs/ADR/0012-jujutsu-parallel-session-isolation.md was Accepted
2026-07-10, adopting Jujutsu as the structural fix, with operational rules now in
docs/GuidanceForClaudeCode.md Β§2a. This paper's branch had diverged from
main before that decision landed later the same evening; corrected to match
the repository's current state of record rather than a stale branch snapshot. Added the
Perspectivity Research Reports disambiguation to Β§7, matching Papers 1, 2, 6, and 7.
|
| 1.3 | July 11, 2026 | Trimmed Β§1's hedge-stacking β five back-to-back negated/contrastive clauses collapsed into a shorter, drier paragraph carrying the same substantive claims (single dated incident, drafted the same week, same peer-aware batch as Paper 7). Reworded Β§7's Perspectivity Research Reports disclaimer into this paper's own phrasing rather than the boilerplate shared with three other papers. |
| 1.4 | July 11, 2026 |
Corrected Β§6's resource-lock timing claim β
scripts/lib/loop-resource-lock.js was introduced in the commit that added
DEVLOOP (c4c9ba02, 2026-07-10 08:40:51), a full calendar day
after the 2026-07-09 incident, not "the same day" as previously stated; restated as
next-morning, matching what git log actually shows. Reworded Β§7's
disclaimer again β the prior "own phrasing" pass still landed on the same shared joke
template (same author/goblin, different noun, "not a shared universe") as four other
papers' disclaimers; now states a claim specific to this paper's own content instead.
Rewrote the closing goblin-voice block β it closed on the same "I keep accurate dated
records, that's the job" beat as Paper 1's, Paper 5's, and Paper 7's closings; now
closes on this paper's own structural-fix point instead.
|
| 1.5 | July 12, 2026 |
Linked Β§6's resource-lock bullet to /workflows/resource-locks.html, the new
diagram-first companion reference for the same mechanism β this paper keeps the incident
narrative, Workflows carries the mechanism diagram.
|
πΊ GOBLIN: Two of him were running at once, in the same room, and the room didn't know it, and for about ten minutes neither did we. Nobody lost anything. That's not the same as nothing almost happening. The fix wasn't a new rule for me to enforce after the fact β it was giving each of him a room of his own before there was anything left for me to write down. I'd rather it stayed boring like this.