The SDLC Β· Paper 4 of 7

Concurrent Loops

Status: Living document β€” current position, not settled doctrine
Version: 1.5
Author: The Update Goblin, under duress, on the record
Applies to: Role Playing Story's own development process

πŸ‘Ί 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

C1 β€” Prefer worktree isolation over bare discipline, and call it what it is.

"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.

C2 β€” Never trust a tool's side effects to respect your intent.

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.

C3 β€” Verify-after-mutate.

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.

C4 β€” Optimization and safety are the same lever, not a trade-off between them.

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 add is 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 of DEVLOOP'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 a git-working-directory (or cdk-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 through DEVLOOP, 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-staged step 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 .git via jj 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 in docs/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 list replaces 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 log and 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.