DEV Community

Dev Dhanadiya
Dev Dhanadiya

Posted on

Why AI Coding Agents Break Git (And How edio Fixes It in Under 5 Milliseconds)

edio terminal UI demo

Here is a scenario every developer using Claude Code, Cursor, or Aider knows too well:

You prompt your AI agent across 7 iterations. Turns 1 to 6 are solid—models are written, database queries work, and tests pass. On Turn 7, you ask for a minor optimization. The agent goes rogue, touches 12 files, breaks imports, and introduces a nasty regression.

Now you're stuck:

  • git restore . fixes the bug, but destroys all the good work from Turns 1 to 6.
  • git commit after every turn fills your Git log with 15 junk commits ("fix typo", "try again", "revert previous").
  • Throwaway branches cause stash conflicts, clutter your repo, and force your IDE language server to re-index.

edio fixes this completely.


What is edio?

edio does not replace Git. It is a single Go binary that works inside your existing .git folder.

It creates an isolated Shadow Commit DAG (refs/edio/*) using low-level Git plumbing. Every time an agent makes an edit, edio takes an invisible turn snapshot in the background.

  • Your staging area (.git/index) is never touched.
  • Your active branch and HEAD never move.
  • Turn 7 breaks? Run edio restore 6 (or press r in the TUI). Your workspace rolls back to Turn 6 in 5 milliseconds.
  • Done with the feature? Run edio accept "feat: add auth". All 7 turns are squashed into one clean commit on your current branch.

60-Second Quickstart

GitHub: github.com/devxdh/edio

1. Install

# macOS / Linux via Homebrew
brew install devxdh/tap/edio

# Or direct install
curl -fsSL https://raw.githubusercontent.com/devxdh/edio/main/install.sh | bash

# Or via Go
go install github.com/devxdh/edio/cmd/edio@latest
Enter fullscreen mode Exit fullscreen mode

2. Initialize in your repo

cd /path/to/your/repo
edio init
Enter fullscreen mode Exit fullscreen mode

This automatically configures:

  • Built-in MCP Server: Auto-injected into .cursor/mcp.json, .vscode/mcp.json, and .gemini/settings.json so models take snapshots and rollbacks autonomously.
  • Claude Code Lifecycle Hooks: Auto-injected into .claude/settings.json to snapshot after every prompt turn.
  • EDIO.md Rules: Instructs scanning LLMs to use edio snapshots instead of polluting raw git commit.

3. Everyday Workflow

# 1. Take a snapshot (if running manually or via CLI)
edio snapshot -m "added token validation"

# 2. Open the split-pane dashboard
edio ui
# -> Use j/k to navigate turns, Tab to scroll diffs, press 'r' to rollback instantly.

# 3. Roll back from CLI (full workspace or a single file)
edio restore 6
edio restore 6 -f middleware.go

# 4. Squash session into your active branch
edio accept "feat(auth): implement token validation and tests"
Enter fullscreen mode Exit fullscreen mode

Complete Command Reference

Command Flags What it does
edio init Sets up .git/edio/, auto-configures MCP servers and Claude hooks.
edio snapshot -m "<msg>" Captures an isolated shadow snapshot of current workspace.
edio run <cmd> Runs any CLI agent command (e.g. edio run aider) and auto-snapshots on exit.
edio ui Opens interactive split-pane TUI (view diffs + 1-key instant rollback).
edio restore <turn> -f <file> Rolls back workspace (or a single file) to any previous turn.
edio log -p Lists turn history for the active session (-p for patch diffs).
edio diff [turn] -f <file> Shows colorized diff for a turn or specific file.
edio accept "<msg>" Squashes all session turns into one clean commit on active branch.
edio gc -d <days> Prunes abandoned shadow sessions older than 10 days (active session is protected).
edio mcp Starts the Model Context Protocol JSON-RPC server.

Under the Hood: How edio Works Internally

If you're wondering how edio records commits, computes diffs, and rolls back files without touching .git/index or moving your branch, here is the technical breakdown.

It all comes down to Git plumbing.

Standard porcelain commands (git add, git commit) couple the working directory, staging area, and branch references together. edio bypasses porcelain entirely and works directly with low-level Git object primitives.

User Workspace ──► Staged via Temporary Index ──► git write-tree ──► Tree Object (SHA)
                        (GIT_INDEX_FILE)                                (In .git/objects)
                              │
                              ▼ (Deleted immediately)
                    Primary .git/index stays clean!
Enter fullscreen mode Exit fullscreen mode

1. Zero-Pollution Snapshotting (pkg/gitengine/tree.go)

Standard git add overwrites your staging index. If you already had files staged for a personal commit, an agent snapshot would wipe that out.

edio avoids this by isolating the index with the GIT_INDEX_FILE environment variable:

// 1. Create a unique scratchpad index inside .git/
tempIndexPath := filepath.Join(gitDir, fmt.Sprintf("edio_index_%d_%s.tmp", time.Now().UnixNano(), hex.EncodeToString(randBytes)))
defer os.Remove(tempIndexPath)

env := []string{fmt.Sprintf("GIT_INDEX_FILE=%s", tempIndexPath)}

// 2. Populate scratchpad from current HEAD and stage workspace changes
runGitWithEnv(env, "read-tree", "HEAD")
runGitWithEnv(env, "add", "-A")

// 3. Write directory tree directly into Git object storage
treeSHA, _ := runGitWithEnv(env, "write-tree")
Enter fullscreen mode Exit fullscreen mode
  • Zero index pollution: The real .git/index is never touched.
  • Native .gitignore support: git add -A respects all ignore rules.
  • Instant cleanup: The temporary index file is deleted immediately.

2. The Shadow Commit DAG (pkg/session/session.go)

To link turns together without moving HEAD or creating temporary branches, edio uses git commit-tree:

func CommitTree(treeSHA, parentSHA, message string) (string, error) {
    args := []string{"commit-tree", treeSHA, "-m", message}
    if parentSHA != "" {
        args = append(args, "-p", parentSHA)
    }
    return RunGit(args...)
}
Enter fullscreen mode Exit fullscreen mode

Snapshots are stored under a private reference namespace:

  • refs/edio/active/<session_id>/<turn_number>
  • refs/edio/active/<session_id>/current
Main Branch (refs/heads/main)
● Commit A (HEAD) ──────────────────────────────────────────────► ● Commit B (Accepted)
                                                                      ▲
Shadow DAG (refs/edio/active/sess_123/*)                              │
  ● Turn 1 ────► ● Turn 2 ────► ● Turn 3 ────► ... ────► ● Turn N ────┘
Enter fullscreen mode Exit fullscreen mode

Because standard Git tools only look at refs/heads/* and refs/tags/*, your shadow DAG is invisible to git log, git branch, and remote pushes.


3. Atomic Promotion on accept (cmd/edio/accept.go)

When you run edio accept "<commit_message>":

  1. Extracts Latest Tree: Reads the tree object from sess.LatestSHA^{tree}.
  2. Creates Official Commit: Runs git commit-tree with the latest tree and the active branch HEAD as its parent.
  3. Advances Branch: Updates refs/heads/<current_branch> directly to the new commit SHA.
  4. Syncs Index: Runs git read-tree HEAD to synchronize the staging index cleanly.
  5. Archives Session: Moves pointers to refs/edio/archive/ and prunes sessions older than 10 days in the background.

4. Storage & Safety Guarantees

  • No Storage Bloat: Unchanged files share existing Git object hashes. Abandoned sessions older than 10 days are pruned automatically.
  • Active Session Lock: The active session is strictly protected from garbage collection.
  • Pure Git Storage: 100% native Git objects. No SQLite, no custom daemons, no external databases.

Summary

edio gives you the safety of micro-versioning with the cleanliness of a single linear commit:

  • Instant rollbacks: Revert full workspace or single files to any turn in milliseconds.
  • Zero pollution: Leaves .git/index and branch history 100% untouched.
  • Automatic agent support: Built-in MCP server, Claude Code hooks, and CLI wrappers.

  • GitHub: Source Code

  • License: MIT

Top comments (0)