Emdash: Parallel Coding Agents with Git Worktree Isolation

March 26, 2026

|repo-review

by Florian Narr

Emdash: Parallel Coding Agents with Git Worktree Isolation

What it does

Emdash is an Electron desktop app that lets you run multiple CLI coding agents — Claude Code, Codex, Gemini, Cursor, and 19 others — in parallel. Each agent gets its own git worktree, so they can work on separate tasks without stepping on each other. YC W26.

Why I starred it

Running one coding agent at a time feels wasteful. You have three bugs to fix and a feature to build — why not run four agents in parallel? The problem is isolation. Two agents editing the same branch will conflict immediately. Emdash solves this with git worktrees: each task gets a full working copy branched from your base, and the agent runs inside it.

The other thing that caught my eye: it's provider-agnostic. The PROVIDERS array in src/shared/providers/registry.ts defines 24 CLI agents with a clean ProviderDefinition type — each entry specifies the CLI binary, auto-approve flag, initial prompt flag, resume flag, and session ID flag. Want to run Claude Code on one task and Codex on another? Just pick from the dropdown.

How it works

The entry point (src/main/entry.ts) does something I don't see often — it monkey-patches Node's Module._resolveFilename to implement path aliases (@shared/*, @/*) without relying on TypeScript's paths at runtime. Simple, zero-dependency, and it means the compiled JS just works in Electron without a bundler step for the main process.

The real engineering is in the worktree system. WorktreePoolService (src/main/services/WorktreePoolService.ts) pre-creates "reserve" worktrees in the background so that when you hit "New Task", you don't wait 3-7 seconds for git worktree add. Instead, it instantly renames the reserve:

// Transform a reserve into a task worktree — two git operations, both instant
await execFileAsync('git', ['worktree', 'move', reserve.path, newPath], {
  cwd: reserve.projectPath,
});
await execFileAsync('git', ['branch', '-m', reserve.branch, newBranch], {
  cwd: newPath,
});

The pool stays fresh through a background polling loop (FRESHNESS_POLL_INTERVAL_MS = 60_000) that checks each reserve's commit hash against the remote via git ls-remote. If the base branch has advanced, it tears down the stale reserve and creates a new one. There's also a 30-minute max age (MAX_RESERVE_AGE_MS) as a safety net.

The TaskLifecycleService (src/main/services/TaskLifecycleService.ts) manages the setup/run/teardown phases for each task. It wraps everything in PTY processes via node-pty, tracks inflight operations with deduplication maps, and handles stop intents so a teardown can wait for the run process to exit gracefully before cleaning up. The buildLifecycleEnv method injects task-specific environment variables — task ID, name, path, default branch, even a deterministic port seed — so lifecycle scripts can reference their context.

The PTY manager (src/main/services/ptyManager.ts) passes through a curated list of 50+ environment variables for agent authentication — every API key from ANTHROPIC_API_KEY to WANDB_API_KEY. On Windows, it patches around a node-pty bug where ConPTY emits EPIPE instead of EIO on pipe breaks, which would otherwise crash the app.

State lives in a local SQLite database managed by Drizzle ORM. The schema (src/main/db/schema.ts) models projects, tasks, conversations, and SSH connections. Each task tracks its worktree path, branch, agent ID, and archive timestamp. Conversations are per-task with a provider field, so you can have multiple chats with different agents on the same task.

Using it

Install on macOS:

brew install --cask emdash

Open a project, create a task, pick a provider. Emdash creates the worktree, spawns the agent PTY, and you're coding. You can pass Linear, GitHub, or Jira tickets directly to the agent.

For remote development, configure an SSH connection in settings and point it at a remote codebase. The app uses ssh2 (with a patched version — patches/ssh2@1.17.0.patch) to manage remote PTY sessions and SFTP file operations.

Rough edges

The project is moving fast — 0.4.47 at time of writing, weekly releases. The WorktreePoolService has a cleanupOrphanedReserves method that scans hardcoded directories (~/cursor/worktrees, ~/Documents/worktrees, ~/Projects/worktrees) on startup. That's fragile — if your projects live somewhere else, orphaned reserves accumulate.

The provider registry duplicates configuration that could come from the CLI agents themselves. Every time a new agent ships a flag change, someone has to update the PROVIDERS array manually. There's no discovery mechanism.

No automated test coverage for the worktree pool or lifecycle service — the test directory has a few unit tests for processTree and RemoteGitService, but the core orchestration code runs untested. For something that does git worktree remove --force and rm -rf on directories, that's a risk.

Bottom line

If you regularly run multiple coding agents and want them isolated from each other, Emdash is the most complete tool for that right now. The worktree pooling is genuinely clever engineering — the kind of optimization that makes the difference between "usable" and "fast enough to not think about."

generalaction/emdash on GitHub
generalaction/emdash