English | 简体中文
Share Claude Code authentication across multiple Linux users on one machine, while keeping per-user session/project data fully isolated.
It also includes a reverse-tunnel workspace mode: publish a
local workspace to a Linux server over SSH, mount it there via sshfs, and
proxy common build/test commands back to your local machine so they consume
your local CPU and memory.
The recommended CLI surface is now split into three layers:
- Server setup:
claude-share server ... - Local client setup:
claude-share client ... - Daily use:
cs ...
sudo claude-share server initAdd more users later:
sudo claude-share server add-user alice
sudo claude-share server add-user bobclaude-share client init st@43.162.99.88This step:
- installs/updates the local CLI
- installs/updates the remote CLI
- generates or reuses an SSH key
- pushes the public key to the server
- verifies local sshd and remote
sshfs - saves a default client profile
cd /your/project
cs liveBy default it:
- publishes the current directory
- opens the reverse SSH tunnel
- auto-picks a free remote port
- mounts the current directory on the server
- enters the remote mount directory
- starts remote
claude
Before jumping into remote claude, cs live now prints a short welcome
summary showing:
- the current server
- the current session
- the main mount path
- any extra mount paths
- the stop command:
cs stop
Example output:
[claude-share] Live mount paths
session myproj-20260419-001500
main /home/st/.claude-share/mounts/myproj-20260419-001500 <= /Users/me/work/myproj
extra /home/st/.claude-share/extras/myproj-20260419-001500/1-data <= /Users/me/data
Need extra directories later:
cs add ~/Documents/data
cs list
cs remove ~/Documents/dataShow the current live session's remote mount paths:
cs whereShow the current status as machine-readable JSON:
cs status --jsonStop the current live session:
cs stopThe problem. You create Linux accounts
useraanduserbso two people can share a server. Each of them logs in, runsclaude, and… has to/loginagain and reconfigure everything from scratch. Claude Code stores its OAuth token in~/.claude/.credentials.json, which lives in each user's private home directory.Naive fix. Point
/home/usera/.claudeat/shared/.claudeand same foruserb. Now two people share their session history, project transcripts, shell snapshots, and command history — and they overwrite each other's state the moment both of them runclaudeat the same time.
claude-share. Share only the four files that actually hold authentication and global configuration; keep the runtime state (projects/,sessions/,history.jsonl, …) strictly per-user. One command creates the Linux users, sets up a shared POSIX group, and wires everything up with symlinks.
Path (under $HOME) |
Shared? | Reason |
|---|---|---|
.claude/.credentials.json |
✅ | OAuth token — the thing you actually want to share |
.claude.json |
✅ | Global feature flags & client settings |
.claude/settings.json |
✅ | Global preferences (effort level, theme, …) |
.claude/settings.local.json |
✅ (optional) | allow lists for CLI permissions |
.claude/projects/ |
❌ | Per-user session transcripts, keyed by cwd |
.claude/sessions/ |
❌ | Running session metadata |
.claude/history.jsonl |
❌ | Command history |
.claude/shell-snapshots/ |
❌ | Per-session shell state |
.claude/plans/, todos/, statsig/, ide/ |
❌ | Per-session or per-install state |
Shared files live exactly once, at /shared/claude-share/home/<relpath>, and
each managed user's $HOME/<relpath> is a symlink to that single copy. When
Claude refreshes the OAuth token it writes the new value through the symlink,
so all users stay in sync with zero extra processes.
The full walkthrough from a fresh machine to a working multi-user setup.
- You are
root, or havesudo. - Claude Code is already installed on the machine and runnable as
claude. - The source user (the account whose auth will be shared, usually
root) has logged in to Claude at least once, so~/.claude/.credentials.jsonexists.
Check the source user's auth file:
sudo ls -l /root/.claude/.credentials.jsonIf it does not exist, run /login once first:
sudo -iu root
claude /login # go through the OAuth flow in your browser
exitgit clone https://github.com/yiancode/claude-share.git
cd claude-shareAfter publishing, the one-liner equivalent is:
curl -fsSL https://raw.githubusercontent.com/yiancode/claude-share/main/install.sh \
| sudo bashSecurity note. Piping
curl | bashtrusts whatever is atmainright now. For production hosts, pin a specific commit SHA:REF=<full-sha> curl -fsSL \ "https://raw.githubusercontent.com/yiancode/claude-share/${REF}/install.sh" \ | sudo REF="${REF}" bash
install.shhonoursREFwhen cloning the repo, so both the installer script and the cloned source will be frozen at the same commit.
sudo ./install.sh
which claude-share # expect: /usr/local/bin/claude-share
claude-share version # expect: claude-share 0.1.0install.sh copies bin/ lib/ commands/ into /usr/local/lib/claude-share/
and symlinks the entry point to /usr/local/bin/claude-share. After this,
the source tree you cloned is no longer required at runtime — editing files
in the clone will not affect the installed copy until you re-run
install.sh.
If you would rather not install and just use the scripts in-place, every
claude-share ... command below can be replaced with
sudo /path/to/claude-share/bin/claude-share ....
sudo claude-share initYou will be prompted for (defaults in brackets; press Enter to accept):
- Shared directory
[/shared/claude-share]— where the single canonical copy of shared auth will live. - Shared group
[claudeshare]— the POSIX group every managed user joins. - Source user
[root]— whose~/.claude/.credentials.jsonis copied in as the initial truth source. - How many users to create now?
[2] - For each user: name, then password (hidden, confirmed twice).
Expected output:
[claude-share] Shared directory [/shared/claude-share]:
[claude-share] Shared group [claudeshare]:
[claude-share] Source user (holds current ~/.claude auth) [root]:
[claude-share] How many users to create now? [2]:
user 1 name: usera
user 1 password: ********
Confirm user 1 password: ********
user 2 name: userb
user 2 password: ********
Confirm user 2 password: ********
[claude-share] Creating shared group and directory...
[claude-share] ✓ Created group: claudeshare
[claude-share] ✓ Added root to group claudeshare
[claude-share] ✓ Shared dir ready: /shared/claude-share
[claude-share] ✓ Created user: usera
[claude-share] ✓ Set password for usera
[claude-share] ✓ Added usera to group claudeshare
[claude-share] ✓ Linked usera to shared auth
[claude-share] ✓ Created user: userb
[claude-share] ✓ Set password for userb
[claude-share] ✓ Added userb to group claudeshare
[claude-share] ✓ Linked userb to shared auth
[claude-share] ✓ Wrote config: /etc/claude-share/config
[claude-share] Done. Managed users: usera userb
sudo claude-share statusEvery row in the per-user link check should be marked ✓. Example:
[claude-share] Per-user links
user: usera
group claudeshare: ✓
✓ .claude/.credentials.json
✓ .claude.json
✓ .claude/settings.json
✓ .claude/settings.local.json
user: userb
group claudeshare: ✓
✓ .claude/.credentials.json
✓ .claude.json
✓ .claude/settings.json
✓ .claude/settings.local.json
If anything is ✗, run sudo claude-share sync and re-check.
su - usera -c 'claude --help'
su - userb -c 'claude --help'Neither should ask you to /login — they are reading the shared credentials
transparently through the symlinks.
sudo claude-share add-user charlie # prompts for passwordOr non-interactively:
sudo claude-share add-user charlie --password 'charlie1234'| Command | What it does |
|---|---|
server init |
Initialize shared Claude auth and the managed multi-user server environment. |
server add-user |
Add another managed Linux user on the server. |
server status |
Show the shared-auth server health status. |
client init <user@host> |
Initialize the local client and bootstrap the remote host. |
client status |
Show the saved local client profile. |
cs live |
Start a live session from the current directory and launch remote Claude. |
cs add <dir> |
Register an extra directory to attach to future cs live sessions. |
cs list |
Show the registered extra directories. |
cs remove <dir> |
Remove a registered extra directory. |
cs where |
Show the main and extra remote mount paths for the current live session. |
cs status |
Show the client profile and local live sessions. |
cs status --json |
Emit the current client profile, active session, and mount paths as JSON. |
cs stop |
Stop the current live session and try to unmount it remotely. |
tunnel ... |
Advanced low-level commands kept mainly for debugging and development. |
Run any command with --help for flags.
These commands still exist, but most users should now prefer:
claude-share client init <user@host>
cs liveThe tunnel ... surface below is mainly for:
- debugging
- diagnosis
- working on claude-share itself
- validating low-level SSH / mount behavior
For day-to-day use, the primary workflow should stay:
sudo claude-share server init
claude-share client init <user@host>
cs live- Local machine: OpenSSH client + server, plus a Bash-compatible shell reachable from SSH login. This is the simplest path on macOS/Linux. On Windows, use OpenSSH Server with a Bash-compatible environment such as Git Bash or MSYS2.
- Server: Linux with
sshfs+ FUSE available. claude-shareshould be installed on both ends.
Local:
claude-share tunnel serve \
--workspace /absolute/path/to/project \
--server user@example.comRemote:
claude-share tunnel mount --session project-20260418-120000
source ~/.claude-share/remote/project-20260418-120000/env.sh
cd ~/.claude-share/mounts/project-20260418-120000
claudeHelpers:
claude-share tunnel status --session project-20260418-120000
claude-share tunnel umount --session project-20260418-120000That is the entire low-level flow. If you do not explicitly need to debug the transport or mount layer, prefer:
claude-share client init <user@host>
cs live- The server side is Linux-only.
- The published workspace is a single directory tree per session.
- Command proxying covers common developer commands, not every possible binary.
- When
tunnel serveexits, the reverse tunnel closes and the uploaded key is removed from the local machine'sauthorized_keys.
Every prompt can be skipped for automation. There are two patterns:
Safe: passwords in a root-owned file. --users-from-file takes a file
of name:password lines (one per line, # comments allowed). The file
must be mode 0600 and owned by root; claude-share refuses anything
else.
umask 077
cat > /root/users.txt <<'EOF'
usera:usera1234
userb:userb1234
EOF
chmod 600 /root/users.txt
sudo claude-share init --yes \
--shared-dir /shared/claude-share \
--group claudeshare \
--users-from-file /root/users.txt
sudo claude-share add-user alice --password-from-file /root/alice.pwUnsafe: passwords in argv. --user name:password and --password <pw>
still work but the password is visible to ps / process accounting for
the lifetime of the command. claude-share prints a warning when you use
these forms. Only acceptable on single-tenant hosts.
sudo claude-share init --yes \
--shared-dir /shared/claude-share \
--group claudeshare \
--user usera:usera1234 \
--user userb:userb1234Post-setup gotcha: re-login required. usermod -aG does not affect
already-running sessions. A managed user who is logged in when
init / add-user runs must log out and log back in before claude
can see the new group membership — otherwise the very first claude
run hits EPERM on the shared credentials file. claude-share prints a
warning at the end of init/add-user reminding you of this.
/shared/claude-share/ drwxrws--- root claudeshare
├── home/ drwxrws--- root claudeshare
│ ├── .claude.json -rw-rw---- root claudeshare
│ └── .claude/
│ ├── .credentials.json -rw-rw---- root claudeshare
│ ├── settings.json -rw-rw---- root claudeshare
│ └── settings.local.json -rw-rw---- root claudeshare
- Directories are
2770so the setgid bit forces every new file to inherit theclaudesharegroup. - Files are
660— group members can read/write, nobody else sees them. claude-shareaddsumask 002to each managed user's~/.bashrcso files they write land as664/775. Without this, one user's writes would be unreadable to the other.
projects/ is keyed by the absolute working-directory path of wherever the
user runs claude. If both users cd into /shared/workspace and run
claude, they would both write to projects/-shared-workspace/… — that
collides regardless of what claude-share does, because those files live in
each user's private $HOME. Recommendations:
- Recommended: each user works in their own home directory or a
user-specific subdirectory of
/shared. - If you must share a working directory, coordinate so only one person runs
claudein it at a time.
Does it survive Claude Code upgrades? Yes. The set of shared items is
defined in lib/paths.sh. If a future version of Claude adds a new auth
file, update that list and re-run sudo claude-share sync.
What about token refresh? When Claude refreshes the OAuth token, it
writes the new value back to .credentials.json. Two behaviours are
possible:
- Write in place (open + truncate + write): the write follows the symlink and lands on the shared inode. Every user's next read picks up the new token. Zero coordination needed.
- Write tempfile +
rename(2):rename(2)replaces the symlink itself with a fresh real file in$HOME. The symlink chain is broken for that user — their next read still works (they own the real file now), but other users are still pointing at the old shared file, which is no longer being updated. This is the "severed symlink" failure mode.
claude-share status detects both broken symlinks and severed symlinks
and tells you to run sudo claude-share sync, which backs up the rogue
local file and restores the symlink. Running status periodically (e.g.
via cron) is recommended on busy multi-user hosts.
Can I share auth but keep settings.local.json private? Yes — pass
--no-settings-local to init, or edit /etc/claude-share/config and
set SHARE_SETTINGS_LOCAL=false then run sudo claude-share sync.
Can I use it with SELinux / systemd-homed / Alpine? The core (symlinks,
POSIX groups, chmod/chown) works anywhere. User creation goes through
useradd/chpasswd, which is fine on Debian/Ubuntu/RHEL/Arch. Alpine uses
adduser by default — patches welcome.
What if two users both need to refresh the token at the same time? The
shared file is a single inode with 660 permissions. At the OS level each
individual write is atomic (either rename or O_TRUNC+write of a small
JSON blob), so the file will not be corrupted. However, claude-share does
not merge or serialise the logical refresh: if both users refresh
concurrently, last-writer-wins on the shared inode. In practice the
window is tiny (refresh takes a few hundred ms and happens rarely), and
both refreshes produce an equivalent fresh token, so this is usually
benign. If you see frequent OAuth errors on a multi-user host, run
claude-share status to check for severed symlinks — those cause many
more user-visible failures than races do.
# Shell-lint
shellcheck --severity=warning bin/claude-share lib/*.sh commands/*.sh install.sh
# Unit tests (no root needed, no real users created)
bats tests/unit
# End-to-end tests (builds a disposable Ubuntu container)
docker build -f tests/integration/Dockerfile -t claude-share-e2e .
docker run --rm claude-share-e2eProject layout:
bin/claude-share # entrypoint
lib/ # common/paths/users/sync/prompt helpers
commands/ # one file per subcommand
tests/unit/ # bats unit tests
tests/integration/ # Dockerfile + e2e.bats
.github/workflows/ # shellcheck + bats + docker e2e
MIT — see LICENSE.