Edit locally.
Run remotely.

Write code on your laptop, run it on the GPU box, lab machine or cloud VM. When the connection drops, nothing freezes and nothing dies.

$ curl -fsSL https://raw.githubusercontent.com/umerghafoor/dev-sync/main/install.sh | sh
train.py — project
devsync
In syncme@gpu-box:~/project
1 running
Next try in 4sRetry now
Activity
↓results/metrics.json2m
↑configs/base.yaml5m
train.pymodel.pybase.yaml
1import torch
2from model import Net
3
4def train(epochs: int = 3):
5 net = Net().cuda() # runs on the remote GPU
6 for epoch in range(epochs):
7 loss, acc = step(net, loader)
8 print(f"epoch {epoch+1}/{epochs} loss {loss:.3f}")

SSH breaks when the Wi-Fi does

The usual way to work on a remote machine hangs everything on one long SSH connection. On hotel Wi-Fi, a VPN or a train, that connection drops, and it takes your work with it.

Your laptop
editor · terminal
$ ssh gpu-box python train.py
typing…
one long SSH connection
✕
Wi-Fi drops
Remote GPU box
where the code runs
python train.py
running · epoch 1/3
The editor freezesVS Code Remote-SSH

Your files live on the server, so every keystroke waits for the network.

The command diesssh host cmd

The process is tied to the session. When the session goes, so does the two-hour run.

Everything by handrsync + ssh

Copy, connect, run, copy results back, and start over when it drops halfway.

The idea: short trips, not one long call

devsync never keeps a connection open. Each step is a separate, quick SSH session that's safe to retry, and your command runs on its own on the remote. A drop just means waiting a moment.

Your laptop
edit at full speed, always
train.pysaved
devsync: in sync
short session · send changed files↑ 2 files · 6 KB
short session · start the job▶ python train.py
short session · collect results↓ output · exit 0 · model.pt
Remote GPU box
devsync agent · nothing installed
python train.py detached
idle
01Only changed files go up
02The job starts on its own
03Wi-Fi drops: the job keeps running
04Back online: devsync catches up
05Output, exit code and files come back
↑src/model.py4.1 KB
↑configs/base.yaml612 B
·data/ (31 GB)not synced
·59 unchanged filesskipped

Only changes move

Files are hashed and compared. Only what changed is sent, compressed, and an interrupted transfer resumes where it stopped.

python train.pyrunning
job on remotelink downyour laptop

Jobs outlive the connection

Commands start detached on the remote. A dropped link only delays the output; the job keeps running and its exit code is kept.

exit 03m 12s on gpu-box
↓checkpoints/model.pt48 MB
↓results/metrics.json2 KB
kepttrain.py— you edited it

Results come back

Output, exit code and the files your command wrote come home. Files you edited meanwhile are never overwritten.

Everything you'd do over SSH

Without babysitting the connection.

data/≥ 31.2 GB
models/1.7 GB
notebooks/25 MB
src/4.1 MB

Huge remotes, your choice

Browse a 50 GB project folder by folder and tick what to sync. Skipped folders aren't even scanned.

train.py — Remote ↔ Local
- lr = 1e-3
+ lr = 3e-4
opt = Adam(net.parameters(), lr)

Diffs like Git

Click any change to compare it with the remote's copy before you send it.

Offline · next try in 4s Retry now

Built for bad links

Changes queue while you're offline and go out with backoff, or right away when you say so.

agentpytest -x
hookdevsync exec -- 'pytest -x'
result42 passedon gpu-box

Coding agents included

A Claude Code hook sends tests, builds and installs to the remote while the agent edits locally.

main↓1 ↑2 · remote
Msrc/model.pystaged
Unotes.md
⌘↵ Commit on remote

The remote's git, here

Stage, commit, switch branches, pull and push the remote repository from VS Code's Source Control.

remotelinux / x86_64
agentuploaded · 1.7 MBverified
installsnone

Nothing to install there

Just SSH. devsync uploads its own small static agent, checks it, and replaces it when you upgrade.

Works where you already work

$ devsync exec -- pytest -x [devsync] pushing 2 file(s), 6.3 KB [devsync] ↑ src/model.py 4.1 KB [devsync] ↑ tests/test_model.py 2.2 KB [devsync] synced 2 file(s), 6.3 KB in 0.8s ============ 42 passed in 3.12s ============ $ devsync submit -- python train.py [devsync] started job 7f3a (runs on its own; no connection is kept open)
Source control · devsync4 actions
Command to run on the remote (⌘Enter)
Not on remote yet2
Mtrain.py⇄ diff
Aconfigs/fast.yaml
Remote jobs2
…python train.py --epochs 50running
✓pytest -xexit 0 · 3m ago
$ devsync git status --short M src/model.py ?? notes.md $ devsync git commit -am "tune lr" [main 4e1c2a9] tune lr 1 file changed, 1 insertion(+), 1 deletion(-) $ devsync git checkout -b experiment Switched to a new branch 'experiment'
$ devsync setup claude # Claude Code edits files locally. Its commands are routed: pytest -x → devsync exec -- 'pytest -x' pip install -e . → devsync exec -- 'pip install -e .' git status → stays local
$ devsync tree me@gpu-box:~/project/ [ ] data/ ≥ 31.2 GB ≥ 88388 files [~] models/ 1.7 GB 214 files [x] notebooks/ 25.2 MB 41 files [x] src/ 4.1 MB 512 files $ devsync select models/checkpoints --pull ↓ pulling ██████████████░░░░░░░░░░ 58% 12/20 files here

Compared

Other good tools cover parts of this workflow. devsync covers the whole loop.

Edit locallySurvives disconnectsRuns commandsResults backRemote install
devsyncYesYesYesYes, conflict-safeNone
VS Code Remote-SSHNoEditor freezesYes—VS Code server
ssh host cmd—Command diesYesNoNone
rsync + watcherYesRetry by handNoBy handrsync
MutagenYesYesNoTwo-way syncAgent

Start in a minute

All you need is SSH access to the machine.

01

Connect a folder

devsync init me@gpu-box:~/project

Any host from ~/.ssh/config works. An empty folder pulls the remote's code first.

02

Run on the remote

devsync exec -- python train.py

Your changes go first. Output streams back and the exit code is yours.

03

Keep it in sync

devsync watch

Or install the VS Code extension and let auto sync do it on every save.

Stop fighting the connection

Free and open source, MIT licensed.