aleixrodriala/GithubDesktopWSL
aleixrodriala/GithubDesktopWSL has 34 GitHub stars and 6 forks. Star changes are not measured yet. Last checked 2026-11-09 14:59 UTC. Last push 2026-08-14.
GitHub Desktop with native WSL support — 6-27x faster git operations
Measured star growth
- in 24h
- Update delayed
- in 7 days
- Update delayed
- in 15 days
- Update delayed
- in 1 month
- Update delayed
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalGitHub Desktop WSL
Think of it like the VS Code Remote WSL extension but for GitHub Desktop. A fork of GitHub Desktop that makes WSL repositories work properly — 16-50x faster git operations, working SSH keys, and no more CRLF issues.
Download the latest release — installs side-by-side with official GitHub Desktop
The problem
Official GitHub Desktop can't handle repos inside WSL. When you open a \\wsl.localhost\... path:
- Git commands are unusably slow — Desktop runs Windows
git.exe, which accesses WSL files through the 9P protocol. Every file stat, read, and open is a round-trip across the VM boundary. - SSH keys don't work — Desktop injects a Windows-only
SSH_ASKPASSbinary that breaks SSH inside WSL. - File operations fail — Checking merge/rebase state, reading diffs, writing
.gitignore— all go through 9P and are either slow or broken. - Deleting repos fails — Windows Recycle Bin doesn't support WSL UNC paths.
The solution
This fork runs a lightweight daemon inside WSL that executes git and file operations natively. Desktop talks to it over TCP instead of going through 9P.
Official Desktop (slow path):
Desktop → git.exe → Windows kernel → 9P → VM boundary → WSL → ext4
(every file operation is a round-trip)
This fork (fast path):
Desktop → TCP → daemon → git (native) → ext4
(one round-trip per command, git has direct disk access)
The daemon is bundled inside the installer. When you open a WSL repo, it's deployed and started automatically. There's nothing to configure.
Why a daemon and not a git shim?
The simpler approach is to replace Desktop's bundled git.exe with a shim that calls wsl.exe -e git. Projects like wsl-git-bridge and my own earlier wsl-git-shim do this. It works, but has limitations:
Spawn overhead — Every command pays ~92ms to launch wsl.exe, even a trivial git rev-parse. The daemon is a persistent process, so it has ~2ms overhead (TCP round-trip + framing). Over a typical Desktop refresh cycle (10-20 commands), that's ~1-2 seconds vs ~20-40ms.
File operations aren't covered — Desktop doesn't just run git. It also reads diffs, checks merge/rebase state, reads and writes .gitignore, checks submodule paths — 8 different files do direct filesystem operations. A git-only shim leaves all of these going through 9P. The daemon handles them natively.
Breaks on Desktop updates — Desktop ships its own bundled git.exe inside each version's Squirrel package. Every update creates a new app-<version> directory with a fresh git.exe, so a replaced shim gets wiped. This fork is a separate app — updates come from its own release channel.
| Daemon (this fork) | Git shim | |
|---|---|---|
| Per-command overhead | ~2ms | ~92ms |
| File operations (diffs, merge state, .gitignore) | Native | Still 9P |
| Survives Desktop updates | Yes | No |
| Install | Run installer | Replace git.exe manually |
Performance
Three ways to run git on a WSL repo, benchmarked across repo sizes:
Spawn overhead
Before any git work happens, each approach has a fixed cost:
| Approach | Overhead |
|---|---|
| Native git (daemon) | 1 ms |
| git.exe (9P) | 46 ms |
| wsl.exe -e (shim) | 94 ms |
Git operations by repo size
| Operation | Small (21 files) | Medium (334 files) | Large (2,373 files) | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Daemon | git.exe (9P) | Shim | Daemon | git.exe (9P) | Shim | Daemon | git.exe (9P) | Shim | |
| git status | 2 ms | 50 ms (25x) | 93 ms (46x) | 2 ms | 51 ms (25x) | 93 ms (46x) | 3 ms | 50 ms (16x) | 92 ms (30x) |
| git log | 2 ms | 50 ms (25x) | 92 ms (46x) | 2 ms | 51 ms (25x) | 91 ms (45x) | 2 ms | 49 ms (24x) | 93 ms (46x) |
| git diff | 2 ms | 49 ms (24x) | 90 ms (45x) | 2 ms | 48 ms (24x) | 91 ms (45x) | 2 ms | 50 ms (25x) | 92 ms (46x) |
| git branch | 1 ms | 50 ms (50x) | 93 ms (93x) | 1 ms | 49 ms (49x) | 92 ms (92x) | 2 ms | 49 ms (24x) | 90 ms (45x) |
| git rev-parse | 1 ms | 51 ms (51x) | 92 ms (92x) | 1 ms | 49 ms (49x) | 92 ms (92x) | 1 ms | 49 ms (49x) | 92 ms (92x) |
| git for-each-ref | 1 ms | 51 ms (51x) | 96 ms (96x) | 1 ms | 51 ms (51x) | 92 ms (92x) | 2 ms | 50 ms (25x) | 91 ms (45x) |
Benchmarks: 11 iterations, median, WSL2 on Windows 11 (kernel 6.6.87.2). Full benchmark script and results.
Real Desktop workflows
These are the actual sequences Desktop runs during common operations:
| Workflow | This fork | Official Desktop | Speedup |
|---|---|---|---|
| Open / switch to a repo | 16 ms | 173 ms | 11x |
| Populate branch list + dates | 6 ms | 88 ms | 14x |
| View last commit diff | 4 ms | 44 ms | 10x |
| Pre-fetch checks | 3 ms | 82 ms | 26x |
The "open repo" workflow includes
git status,for-each-ref, 4pathExistschecks,readFile, andgit log.
Why the difference is so large
git.exe running on a WSL repo has a ~50ms floor for any operation — that's the cost of accessing files through 9P across the VM boundary. A wsl.exe -e git shim avoids 9P but pays ~92ms to spawn wsl.exe per command — actually slower than 9P for typical Desktop operations. The daemon has ~2ms overhead (TCP round-trip + message framing). The actual git work is the same; the difference is entirely in how commands are dispatched and files are accessed.
A typical Desktop refresh runs 10-20 commands. With git.exe that's 500-1000ms of pure overhead. With a shim it's 920-1840ms. With the daemon it's 20-40ms.
Install
- Download GitHubDesktopWSLSetup-x64.exe
- Run the installer
- Open any WSL repository
That's it. The app:
- Detects WSL paths automatically (
\\wsl.localhost\...or\\wsl$\...) - Deploys the daemon to
~/.local/bin/in your WSL distro on first use - Starts the daemon via
--daemonize(forks to background) - Reconnects automatically if the daemon crashes
- Falls through to normal Desktop behavior for Windows repos
SmartScreen may warn on first install (the fork is unsigned). Click "More info" then "Run anyway".
How it works
Components
wsl-git-daemon (C, ~550 lines, zero dependencies)
A persistent daemon that runs inside WSL. Listens on TCP 127.0.0.1 on a random port. Handles:
- Git commands — forks git with proper pipes for stdout/stderr/stdin
- File operations — read, write, stat, pathExists, unlink via direct syscalls
- Security — token-based auth, localhost-only connections
wsl.ts (TypeScript, ~420 lines)
The Desktop-side client. Responsibilities:
- Path detection — identifies
\\wsl.localhost\...and\\wsl$\...paths, extracts distro name - Lifecycle management — deploy binary, start/restart daemon, health check
- Protocol client — binary length-prefixed frames over TCP
- Drop-in wrappers —
wslReadFile(),wslPathExists(), etc. that route to daemon for WSL paths and fall through to nativefsfor Windows paths
core.ts patch (1 check)
All git commands in Desktop flow through git() in core.ts. A single if (isWSLPath(path)) routes WSL repos through the daemon while leaving Windows repos completely untouched.
Wire protocol
Frame: [type: 1 byte][length: 4 bytes big-endian][payload: N bytes]
Client → Daemon:
INIT (0x01) JSON { token, cmd, args, cwd, stdin, path }
STDIN (0x02) Raw bytes (for writeFile content, empty = EOF)
Daemon → Client:
STDOUT (0x03) Raw bytes (git output / file content)
STDERR (0x04) Raw bytes
EXIT (0x05) 4-byte exit code
ERROR (0x06) UTF-8 error message
STAT_RESULT (0x07) JSON { exists, size, isDir }
Daemon lifecycle
User opens WSL repo
→ Desktop detects \\wsl.localhost\Ubuntu\...
→ Extracts distro name ("Ubuntu")
→ Tries to connect to daemon (reads /tmp/wsl-git-daemon.info via UNC path)
→ If not running:
→ Deploys binary: wsl.exe -d Ubuntu -e sh -c "cp ... ~/.local/bin/ && chmod 755 ..."
→ Starts daemon: wsl.exe -d Ubuntu -e sh -c "~/.local/bin/wsl-git-daemon --daemonize"
→ Daemon forks, writes info file, parent exits, wsl.exe returns
→ Desktop reads info file, connects to daemon
→ All git/file operations go through daemon
→ If connection fails mid-session → auto-restart
Staying current with upstream
This project doesn't diverge from upstream GitHub Desktop. It maintains a set of patch files (in patches/) that are applied on top of each upstream release:
- CI checks for new upstream
release-*tags every 6 hours - Clones
desktop/desktopat the new release tag - Applies
patches/*.patchviagit apply - Builds the daemon + app, publishes a new release
- If patches fail to apply → opens a GitHub Issue for manual resolution
The patches are designed to be minimal and conflict-resistant: one new file (wsl.ts), one new directory (wsl-daemon/), and small surgical changes to existing files (mostly import swaps). All patches are plain text and easy to review.
Files changed
NEW wsl-daemon/daemon.c Persistent daemon
NEW wsl-daemon/Makefile Build script
NEW app/src/lib/wsl.ts Client, lifecycle, wrappers
PATCH app/src/lib/git/core.ts Git routing (1 if-block)
PATCH app/src/main-process/main.ts WSL delete handler
PATCH app/src/models/repository.ts isWSL getter
PATCH app/src/lib/git/diff.ts Import swap: wslReadFile
PATCH app/src/lib/git/rebase.ts Import swap: wslReadFile, wslPathExists
PATCH app/src/lib/git/cherry-pick.ts Import swap: wslReadFile, wslPathExists
PATCH app/src/lib/git/merge.ts Import swap: wslPathExists
PATCH app/src/lib/git/description.ts Import swap: wslReadFile, wslWriteFile
PATCH app/src/lib/git/gitignore.ts WSL-aware read/write/unlink
PATCH app/src/lib/git/submodule.ts Import swap: wslPathExists
PATCH app/src/lib/stores/app-store.ts Import swap: wslPathExists
PATCH app/package.json Branding: "GitHub Desktop WSL"
PATCH script/dist-info.ts Update URL, app ID
PATCH script/build.ts Bundle daemon binary
PATCH script/package.ts Skip code signing
NEW .github/workflows/sync-upstream.yml Auto-sync with upstream
NEW .github/workflows/build-release.yml Build and publish releases
Most patches are one-line import changes — swapping readFile from fs/promises with wslReadFile from wsl.ts. These are unlikely to conflict with upstream changes.
Building from source
# 1. Build daemon (in WSL)
cd wsl-daemon && make
# 2. Install dependencies (on Windows)
yarn install
# 3. Build
yarn build:dev # development
yarn build:prod # production
# 4. Package installer (production only)
SKIP_CODE_SIGNING=1 yarn package
License
Credits
Based on GitHub Desktop by GitHub, Inc.
WSL support by @aleixrodriala.
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
561,625 starspublic-apis/public-apis
A collective list of free APIs
497,970 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
450,201 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,303 stars