DEV Community

孫昊
孫昊

Posted on

Concurrent autoiter sessions force-pushed each other - here's the recovery pattern

I ran two AI agent sessions in parallel last week. Both operating on the same git repo. Both pushing to origin/main. I didn't expect them to collide.

They did. The second agent's push force-overwrote the first agent's commits. Four files, 30 minutes of work, gone from history.

This isn't a hypothetical "don't run concurrent agents on the same repo" warning. It's a real failure mode I had to recover from, and the pattern I used to get back to a clean state in under 5 minutes.

Here's what happened and how to prevent it.

The collision

Agent A (session 1): git fetch origin main; git rebase origin/main; git push origin main

Agent B (session 2): git fetch origin main; git rebase origin/main; git push origin main

If Agent A pushes first, origin/main advances. But Agent B already fetched before Agent A pushed, so Agent B's rebase is against the old origin/main. When Agent B pushes, its commits don't include Agent A's, so Git does a force-overwrite.

The result: (forced update) in the git log, and Agent A's commits are orphaned.

This is invisible in real time. The second agent's push succeeds (no errors). You only find out hours later when you notice missing files.

Detection: the telltale sign

After any git push or git pull, check for forced updates:

git log --oneline -10 | grep "forced update"
Enter fullscreen mode Exit fullscreen mode

Or check the reflog:

git reflog | head -5
Enter fullscreen mode Exit fullscreen mode

If you see something like:

abcd123 (HEAD -> main) HEAD@{0}: rebase: fast-forward
efgh456 HEAD@{1}: reset: moving to origin/main
Enter fullscreen mode Exit fullscreen mode

And the first commit hash doesn't match origin/main anymore, you've been force-pushed.

Better: detect it before it happens. After any fetch, check for divergence:

git fetch origin main
git log --oneline HEAD..origin/main
Enter fullscreen mode Exit fullscreen mode

If there are commits in origin/main that aren't in your HEAD, you've diverged. Don't push until you rebase or merge.

The recovery pattern (3 steps)

I recovered by:

Step 1: Save your work (if you have uncommitted changes)

git stash
Enter fullscreen mode Exit fullscreen mode

This clears the working directory. If you need to preserve your uncommitted state, use a temporary branch:

git checkout -b recovery-temp
git commit -am "WIP: preserving uncommitted state"
Enter fullscreen mode Exit fullscreen mode

Step 2: Reset to the last known-good commit

# Find the commit before the force-push
git reflog | grep "reset\|rebase"
# Output: abcd123 (was the rebase commit, now orphaned)

# Reset to the commit that was force-pushed over
git reset --hard abcd123
Enter fullscreen mode Exit fullscreen mode

Now your local branch is at the commit that got overwritten. But origin/main has moved on.

Step 3: Re-create the orphaned commits

This is the part that requires context. If the overwritten commits are in your session memory (which they were for me — the AI agent that created them had the content in conversation context), you can re-write the files:

# Reconstruct the lost files from your context
cat > lost_file_1.md << 'EOF'
[paste the content of the orphaned file]
EOF

cat > lost_file_2.md << 'EOF'
[paste the content]
EOF

# Re-commit
git add lost_file_1.md lost_file_2.md
git commit -m "Recovery: restore orphaned files from concurrent agent collision"

# Now rebase on top of the new origin/main
git fetch origin main
git rebase origin/main
git push origin main
Enter fullscreen mode Exit fullscreen mode

Full recovery script

If you need to script this (and you will, if you run concurrent agents):

#!/bin/bash
# recover_from_force_push.sh

set -e

echo "Detecting force-push collision..."

# Step 1: Fetch latest
git fetch origin main

# Step 2: Check if origin/main is ahead of local
COMMITS_AHEAD=$(git log --oneline HEAD..origin/main | wc -l)
COMMITS_BEHIND=$(git log --oneline origin/main..HEAD | wc -l)

if [ "$COMMITS_BEHIND" -gt 0 ] && [ "$COMMITS_AHEAD" -gt 0 ]; then
    echo "✗ Divergence detected: $COMMITS_BEHIND local, $COMMITS_AHEAD remote"
    echo ""
    echo "Local commits (orphaned):"
    git log --oneline origin/main..HEAD
    echo ""
    echo "Remote commits (will be force-pushed):"
    git log --oneline HEAD..origin/main
    echo ""
    echo "⚠ This is a force-push collision. Recovering..."
else
    echo "✓ No divergence detected"
    exit 0
fi

# Step 3: Save current HEAD ref for recovery
LOCAL_HEAD=$(git rev-parse HEAD)
echo "Saved local HEAD: $LOCAL_HEAD"

# Step 4: Reset to remote
echo "Resetting to origin/main..."
git reset --hard origin/main

# Step 5: Prompt user to restore from context
echo ""
echo "✓ Local HEAD saved: $LOCAL_HEAD"
echo "✓ Reset to origin/main"
echo ""
echo "To restore orphaned commits:"
echo "  1. Retrieve the lost files from your session context"
echo "  2. Add them back: git add [files]"
echo "  3. Commit: git commit -m 'Recovery: restore from collision'"
echo "  4. Push: git push origin main"
echo ""
echo "Or if the commits are entirely redundant, you can safely ignore this."
Enter fullscreen mode Exit fullscreen mode

Save this as recover_from_force_push.sh, chmod +x, and run it when you detect a collision.

Prevention: the long-term fix

For concurrent agents, add this check to each agent's startup:

# Before any commits, verify no divergence
git fetch origin main
DIVERGE=$(git log --oneline HEAD..origin/main | wc -l)

if [ "$DIVERGE" -gt 0 ]; then
    echo "⚠ Divergence detected. Aborting push to prevent collision."
    echo "  Run: git rebase origin/main && git push origin main"
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Or, simpler: run concurrent agents on separate branches, then merge them sequentially:

# Agent A
git checkout -b agent-a-work
# ... do work, commit, push
git push origin agent-a-work

# Agent B (different branch)
git checkout -b agent-b-work
# ... do work, commit, push
git push origin agent-b-work

# Then, in your main process, merge them back to main
git checkout main
git merge agent-a-work
git merge agent-b-work
git push origin main
Enter fullscreen mode Exit fullscreen mode

This way, each agent has its own branch. No collision. No force-pushes.

What I changed after this incident

  1. Manifest-first discovery: Every new file has YAML frontmatter. If a file disappears, I detect it immediately (missing from dashboard index).
  2. Reflog monitoring: Before any agent session pushes, it logs the reflog entry. If the hash changes unexpectedly, the agent stops and alerts me.
  3. Session context retention: The AI agent keeps its working context (file contents, commit messages) available for recovery. If a push fails, it can re-write the lost files.

The meta-lesson: concurrent writes to the same resource require detection + recovery, not just prevention. You can write all the "don't do this" rules you want, but in practice, agents running on real deadlines will create race conditions. Make sure your recovery path is fast.

For indie developers running autoiter

If you're running /autoiter sessions on your own project:

  • Single agent per branch: One session per topic/branch. Merge them back sequentially.
  • Detect divergence early: After every git fetch, check git log HEAD..origin/main.
  • Commit messages as breadcrumbs: Include the session ID or direction in each commit. Makes recovery easier if you need to back out.
git commit -m "Feature: X from autoiter-session-$SESSION_ID"
Enter fullscreen mode Exit fullscreen mode

Then if you need to back out the session, you can git revert <session-id-commits> without touching unrelated work.


Full recovery script is in my devops-tools repo.

If you're running concurrent automation on git, drop a comment — curious what collision patterns other people have hit.

Sources:

Top comments (0)