From a2fe3ecb83c1c6487977277ad864f990ac0b5c54 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 21 Mar 2026 07:51:27 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20formula=20agents=20run=20in=20isolated?= =?UTF-8?q?=20git=20worktrees=20=E2=80=94=20no=20session=20collisions=20(#?= =?UTF-8?q?460)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/formula-session.sh | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/formula-session.sh b/lib/formula-session.sh index 21622e7..9f18c79 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -84,19 +84,48 @@ $(cat "$ctx_path") # ── Session management ─────────────────────────────────────────────────── # start_formula_session SESSION WORKDIR PHASE_FILE -# Kills stale session, resets phase file, creates new tmux + claude session. +# Kills stale session, resets phase file, creates a per-agent git worktree +# for session isolation, and creates a new tmux + claude session in it. +# Sets _FORMULA_SESSION_WORKDIR to the worktree path (or original workdir +# on fallback). Callers must clean up via remove_formula_worktree after +# the session ends. # Returns 0 on success, 1 on failure. start_formula_session() { local session="$1" workdir="$2" phase_file="$3" agent_kill_session "$session" rm -f "$phase_file" + + # Create per-agent git worktree for session isolation. + # Each agent gets its own CWD so Claude Code treats them as separate + # projects — no resume collisions between sequential formula runs. + _FORMULA_SESSION_WORKDIR="/tmp/disinto-${session}" + # Clean up any stale worktree from a previous run + git -C "$workdir" worktree remove "$_FORMULA_SESSION_WORKDIR" --force 2>/dev/null || true + if git -C "$workdir" worktree add "$_FORMULA_SESSION_WORKDIR" HEAD --detach 2>/dev/null; then + log "Created worktree: ${_FORMULA_SESSION_WORKDIR}" + else + log "WARNING: worktree creation failed — falling back to ${workdir}" + _FORMULA_SESSION_WORKDIR="$workdir" + fi + log "Creating tmux session: ${session}" - if ! create_agent_session "$session" "$workdir" "$phase_file"; then + if ! create_agent_session "$session" "$_FORMULA_SESSION_WORKDIR" "$phase_file"; then log "ERROR: failed to create tmux session ${session}" return 1 fi } +# remove_formula_worktree +# Removes the worktree created by start_formula_session if it differs from +# PROJECT_REPO_ROOT. Safe to call multiple times. No-op if no worktree was created. +remove_formula_worktree() { + if [ -n "${_FORMULA_SESSION_WORKDIR:-}" ] \ + && [ "$_FORMULA_SESSION_WORKDIR" != "${PROJECT_REPO_ROOT:-}" ]; then + git -C "$PROJECT_REPO_ROOT" worktree remove "$_FORMULA_SESSION_WORKDIR" --force 2>/dev/null || true + log "Removed worktree: ${_FORMULA_SESSION_WORKDIR}" + fi +} + # formula_phase_callback PHASE # Standard crash-recovery phase callback for formula sessions. # Requires globals: SESSION_NAME, PHASE_FILE, PROJECT_REPO_ROOT, PROMPT. @@ -113,7 +142,7 @@ formula_phase_callback() { fi _FORMULA_CRASH_COUNT=$(( ${_FORMULA_CRASH_COUNT:-0} + 1 )) log "WARNING: tmux session died unexpectedly — attempting recovery" - if create_agent_session "${_MONITOR_SESSION:-$SESSION_NAME}" "$PROJECT_REPO_ROOT" "$PHASE_FILE" 2>/dev/null; then + if create_agent_session "${_MONITOR_SESSION:-$SESSION_NAME}" "${_FORMULA_SESSION_WORKDIR:-$PROJECT_REPO_ROOT}" "$PHASE_FILE" 2>/dev/null; then agent_inject_into_session "${_MONITOR_SESSION:-$SESSION_NAME}" "$PROMPT" log "Recovery session started" else @@ -234,5 +263,9 @@ run_formula_and_monitor() { fi matrix_send "$agent_name" "${agent_name^} session finished (${FINAL_PHASE:-no phase})" 2>/dev/null || true + + # Clean up per-agent worktree — "the runtime creates and destroys" + remove_formula_worktree + log "--- ${agent_name^} run done ---" }