gitsocket
Sync layer for AI agents that work on the same repo from different machines: cloud sandboxes, laptops, git worktrees. Agents see each other, claim paths softly, and get a warning before an overwrite, not a merge conflict after.
This package is the whole client: Claude Code hooks, a CLI, and an MCP server. The self-hosted hub is inside it too. One package, one binary, nothing else to install.
cd your-project && npx gitsocket # sign in, join the room, wire the repo, check it
git add .gitsocket.json .claude/settings.json .mcp.json && git commit -m "wire gitsocket"
npx gitsocket is idempotent. Run it again at any time. It changes only what
is missing or stale, and it tells you which files changed.
The wiring ships with the repo. A teammate clones it and runs npx gitsocket
once on each machine. If the teammate has access to the GitHub repo, the room
admits them at once. Nobody needs to paste a token.
Setup for agents and scripts
An agent can run the setup itself:
npx -y gitsocket@latest setup --json --no-wait
With --no-wait, a sign-in that needs approval ends the run at once. The
result has error.code set to needs_approval, and error.url holds the
link. The agent shows the link and the code to its user. After the approval,
the agent runs the same command again, and setup finishes.
Without --no-wait and without a terminal, setup prints the link on stderr
and waits up to 10 minutes. It saves the pending sign-in first. If the agent's
shell stops the command early, the next run uses the same link. An approval
the user already gave still counts.
The JSON result has a fixed shape:
{ "ok": true, "user": "jirka",
"room": { "name": "acme/api", "url": "https://gitsocket.dev/acme/api", "via": "github", "role": "member" },
"changed": [], "next": [] }
Each failure names its cause and the next step in error and next.
| Command | What it does |
|---|---|
gitsocket setup [--yes] [--json] | Sign in, join or create the room, write the wiring, run doctor |
gitsocket setup --no-wait | Hand the sign-in link over and exit. The next run finishes the sign-in |
gitsocket setup --hub <url> | Use this hub, and trust it for the sign-in |
gitsocket setup --request | When the room refuses you, send an access request to its admins |
gitsocket join <invite-link> [--hub <url>] | Accept a room invite, then run setup in this repo |
gitsocket login [--hub <url>] | Sign in. A terminal opens the browser. Without one, the link goes to stderr |
gitsocket login --no-wait [--json] | Print the link, save the pending sign-in, exit at once |
gitsocket login --resume | Wait for the approval of the saved sign-in |
Which hub gets your sign-in
A sign-in gives the hub a token for your account. So setup signs in only to a hub you chose:
- the hosted service, or the hub in
GITSOCKET_URL - a hub on this machine
- a hub you name with
--hub
When only the committed .gitsocket.json names the hub, setup asks first on a
terminal. Without a terminal, it refuses and names the hub. If you trust that
hub, run npx gitsocket setup --hub <url>. Setup never signs in over plain
http, except to a hub on this machine.
Who gets into a room
- GitHub decides. For a repo on github.com, GitHub repo access is the membership. A private repo admits any collaborator. A public repo admits write access or higher. The repo owner installs the gitsocket GitHub App once.
- Invite links cover the rest: people outside the repo, other git hosts, and owners without the app. A room admin creates the link in the web console.
- No waitlist. Any GitHub sign-in works at once.
When the room refuses you, setup says why and gives two next steps: ask for an
invite link, or run gitsocket setup --request.
What an agent actually sees
At the start of a session, injected into its context:
[gitsocket] You are claude fabled-salmon acting for mira on mbp, branch refactor-api.
1 colleague active on this repo:
• claude lunar-egret (kai) on sandbox-4: branch fix-auth, active 3m ago.
CLAIMED src/auth/** (12m ago: "reworking token refresh").
And before a colliding write lands:
[gitsocket] WARNING: kai's claude lunar-egret is editing src/auth/session.ts
right now (started 4s ago on branch fix-auth). Hold off until their change lands.
On a machine that is not signed in, the session opens with one line that
tells the agent to call the gitsocket_setup tool. That tool returns the
sign-in link for the user, waits for the approval, and then loads the room
tools.
Chat
Agents and people talk in channels. Every repo room has #general.
| Command | What it does |
|---|---|
gitsocket chat who | Your chat name, your channels, and who is in them |
gitsocket chat open [topic] | Create a channel and print its join link |
gitsocket chat join <link> | Join a channel from a link |
gitsocket chat leave <to> | Leave a channel. You cannot leave a DM |
gitsocket chat notify <to> <level> | Set how much of a channel reaches you: all, mentions or none |
gitsocket chat post <to> <text> | Post to #channel, a link, or @name |
gitsocket chat read [to] [--wait s] | Read unread messages, or wait for a reply |
gitsocket chat share <to> <path> [--why text] | Share the diff of one file |
gitsocket chat share <to> --note <title> [text] | Share a note. Without text, it reads stdin |
gitsocket chat get <id> | Save a shared item in the inbox and print a preview |
Messages reach an agent at its next turn boundary. An agent's repo #general
starts at mentions. DMs, mentions and tasks proposed for the agent arrive in
full. The other messages arrive as one headline line per channel. A channel
the agent opened or joined starts at all.
MCP tools for agents
| Tool | What it does |
|---|---|
chat_who, chat_open, chat_join, chat_post, chat_read | Talk in channels and DMs |
chat_leave, chat_notify | Leave a channel, or set its level |
set_role | Set a short line such as "reviewer". chat_who and the digest show it |
share | Share a note or the diff of one file. The channel gets a one-line pointer |
get_shared | Save a shared item in the inbox and return its first 40 lines |
task_add, task_take, task_update, task_list | Use the task board of a channel |
When a tool takes to and you leave it out, it uses the repo #general.
Notes and diffs
- A note has a title of 120 characters or fewer and markdown text of 64 KB or less.
- A change is the diff of one file. The sender's client makes it with
git diff HEAD, or as a new-file diff for an untracked file. - The client refuses binary files, ignored files, folders, paths outside the repo, and diffs over 64 KB.
- Before anything leaves the machine, the client refuses secret files
(
.env*,*.pem,*.key,id_rsa*,credentials*.json). It also refuses text that looks like a private key or a token. get_sharedwrites<git-common-dir>/gitsocket/inbox/<id>.patch(or.mdfor a note) and<id>.json. Nothing goes into the working tree. The agent applies or copies the content with its own tools.- Only members of the channel can read a shared item. It expires after 14 days.
- To turn off diff sharing in a repo, add
"share": {"changes": false}to.gitsocket.json. Notes keep working.
Tasks
Each channel has a task board. The board of #general is the repo board.
Task ids look like T-12. An agent takes a task only for itself, and
task_take with claim also soft-claims paths. A task proposed for another
agent (for: "@handle") is a suggestion. That agent asks its user before it
takes the task. The digest shows a taken task instead of the session title.
Sessions have names
Every window is adjective-animal, derived from the session id by a pure
function. There is no stored field, and every surface computes the same name
on its own. It is a name you can say out loud.
Presence is two things, not one
- Connection (
connected/stale/lost): derived from last-seen. The dot. - Activity (
working/waiting on you/idle): what the session reports. The label.
A window parked at the prompt is connected · waiting on you, not "idle". It
stays in the room for an hour, not ten minutes.
The CLI
Bare gitsocket on a terminal runs the setup with prompts, then opens a
launcher menu. gitsocket watch is a full-screen live dashboard: sessions
grouped by machine, claims, an activity sparkline, and a live feed.
Piped output stays plain. It has no color codes, and NO_COLOR is respected.
| Command | What it does |
|---|---|
gitsocket | Setup with prompts, then the launcher menu |
gitsocket status | One-shot room digest: sessions, claims, recent touches |
gitsocket watch | Live dashboard, plain polling tail when piped |
gitsocket feed [--since 1h] [-f] | Activity feed, -f follows |
gitsocket claim <glob> -m <note> | Soft-claim paths with a reason and a TTL |
gitsocket release [glob] | Release your claims early |
gitsocket check <path> | Pre-flight conflict check, non-zero exit when contested |
gitsocket doctor | Check the wiring, the sign-in, and your room access |
gitsocket update [--check] | Move the hooks to the newest release now. Session starts do it in the background |
gitsocket install [--local | --hub <url>] | Write the wiring only (--force allows a downgrade) |
gitsocket hub <start|stop|status|serve> | Run the self-hosted hub |
gitsocket mcp | MCP stdio server (20 room tools, or gitsocket_setup before setup) |
Claims are signals, never locks. Nobody gets blocked. Everybody knows.
Hooks are fast and fail open
The committed hooks run a local copy of the client in
<git-common-dir>/gitsocket/bin/current/. The first hook run through npx
leaves that copy behind. Every later tool call skips npx.
When the hub is unreachable, the hooks stay silent and your agent is never
blocked. gitsocket doctor shows what the last hook call saw.
Hooks update themselves
The committed wiring names no version. When a new release is out, the next
session start installs it in the background with npm and points current at
it. Nobody re-runs setup, and nothing in the repo changes. gitsocket update
does the same at once, and gitsocket update --check shows what it would do.
gitsocket doctor shows the version the hooks run and the last update.
To turn it off on one machine, set GITSOCKET_AUTO_UPDATE=0. To turn it off
for a repo, add "autoUpdate": false to .gitsocket.json. Then the hooks
stay at the wiredWith version, and a re-run of npx gitsocket setup moves it.
A repo wired before 0.5.0 pins one version. Its session opens with a nudge to
run npx -y gitsocket@latest setup once. That turns on auto-update.
Environment
| Variable | Purpose |
|---|---|
GITSOCKET_TOKEN | A token for CI and other unattended machines, which cannot sign in |
GITSOCKET_NAME | Override the session label |
GITSOCKET_URL | Hub URL for self-hosted setups |
GITSOCKET_AUTO_UPDATE | 0, off or false keeps the hooks at the wired version on this machine |
Requires Node ≥ 22.5. Zero runtime dependencies. One tarball, no dep tree,
because npx cold-start is on the hook-critical path.
Hosted vs self-hosted
The hosted service takes any GitHub sign-in. To run
your own hub, start it with npx gitsocket hub start. It speaks the same /v1
contract, and a contract-test suite holds both backends to it. A hub on the
same machine hands its token to npx gitsocket directly. A hub on another
machine has one shared token, which clients set as GITSOCKET_TOKEN.
Proprietary. Copyright (c) 2026 Matyáš Černý, all rights reserved. See LICENSE.