Skip to content

Instantly share code, notes, and snippets.

@joelnet
Last active May 2, 2026 00:31
Show Gist options
  • Select an option

  • Save joelnet/370bd647944df9b15394f21270c0cadf to your computer and use it in GitHub Desktop.

Select an option

Save joelnet/370bd647944df9b15394f21270c0cadf to your computer and use it in GitHub Desktop.
Claude skill to tranfer files / directories between agents using croc
name croc-transfer
description Send or receive a file between two machines using croc (https://github.com/schollz/croc). Use this skill whenever the user wants to transfer, send, share, copy, ship, or grab a file between two hosts — desktop ↔ server, laptop ↔ Raspberry Pi, between two computers in different places, between two people, or between two agents/terminals — even if they don't say "croc" but describe the workflow ("get this file over to my pi", "ship the build artifact to the other box", "have someone else download this CSV from me", "I need to receive a file from another machine"). Croc works peer-to-peer when a direct path exists (LAN, Tailscale, WireGuard) and falls back to a public TCP relay otherwise. The skill handles installation, picks a code phrase, runs the right command for the user's role (sender or receiver), and reads the transfer logs to surface whether bytes went peer-to-peer or via the public relay. The skill is transport-agnostic about how the code phrase reaches the other party — that can be chat, paste, SSH, a tmux pane, anything.

croc-transfer

Transfer files between two hosts using croc. Croc is a single binary that brokers a TCP connection through a public relay, with opportunistic upgrade to a direct path when one is reachable. Bytes are end-to-end encrypted (PAKE + AES-256-GCM) — the relay only sees ciphertext.

When to use it

The user wants to move a file from one machine to another. Common phrasings: send this to the other box, transfer X.png to my Pi, share this CSV with my colleague, I need to receive a file from another machine, get this file across, ship the artifact. Whether they're communicating with the receiver via chat, SSH, a phone call, or another agent in another terminal — that's outside this skill's concern. This skill cares about the croc commands.

Skip this skill when the user has explicitly named a different tool (scp, rsync, cloud upload, Git), when it's an in-process file copy on one machine, or when they want to paste text content into the conversation.

The mental model

Two roles:

  • Sender opens a file, picks a code phrase, and waits for someone to claim it.
  • Receiver quotes the same code phrase and pulls the file.

They meet "in a room" identified by the code phrase, brokered by the relay. The user must communicate the code phrase to the other party somehow (chat, SSH, paste, etc.). That communication is not this skill's job — this skill's job is the croc commands themselves and surfacing what happened.

If you're the only operator (controlling both sides — e.g., across SSH, or across two terminals you both have open), you'll run sender on one host and receiver on the other. If you're operating just one side, you'll need the user to tell you the code phrase (if they're receiving) or to relay it to the other party (if they're sending).

Step 1 — make sure croc is installed

The convention here is ~/.local/bin/croc. Check first:

~/.local/bin/croc --version

If that fails, install it. Architecture matters — installing the wrong build is silently broken. Detect with uname -m and uname -s:

uname -s uname -m Asset suffix
Linux x86_64 Linux-64bit
Linux aarch64 or arm64 Linux-ARM64
Linux armv7l Linux-ARM
Darwin x86_64 Darwin-64bit
Darwin arm64 Darwin-ARM64
mkdir -p ~/.local/bin && \
VER=v10.4.2 && SUFFIX="<asset suffix from table>" && \
curl -fsSL -o /tmp/croc.tar.gz \
  "https://github.com/schollz/croc/releases/download/${VER}/croc_${VER}_${SUFFIX}.tar.gz" && \
tar -xzf /tmp/croc.tar.gz -C /tmp croc && \
install -m 755 /tmp/croc ~/.local/bin/croc && \
~/.local/bin/croc --version

If you control both ends, install on both. If both versions don't match, transfers usually still work but it's worth aligning if you can.

Step 2 — pick a fresh code phrase

A code phrase is the room name on the relay. Two transfers with the same phrase will collide if they overlap, and stale phrases sometimes hit reused-room errors. Always generate a new one per transfer. Format: lowercase letters, hyphens, ≥6 chars. Examples: garden-fence-blue, quick-share-2026, report-final-99. A timestamp-suffixed phrase is fine: share-$(date +%s).

Step 3a — sending

Croc 10.x removed the --code CLI flag for security. The phrase must travel via the CROC_SECRET env var:

CROC_SECRET=<phrase> ~/.local/bin/croc send <path-to-file>

Foreground (interactive) is fine if you're sitting at the terminal. Background is better when you're scripting or coordinating with another process:

CROC_SECRET=<phrase> nohup ~/.local/bin/croc send <path-to-file> > /tmp/croc-send.log 2>&1 &
SENDER_PID=$!
sleep 3
ps -p $SENDER_PID -o pid,stat,cmd  # confirm still alive
cat /tmp/croc-send.log              # should print "Sending '<file>'"

For directories, add --zip (croc tars + zips before sending):

CROC_SECRET=<phrase> ~/.local/bin/croc send --zip <path-to-dir>

After starting the sender, tell the receiver the code phrase. How you tell them depends on context — chat, copy/paste, a terminal you both see, doesn't matter to croc.

When surfacing the code phrase back to the user (so they can pass it to whoever is receiving), use this exact phrasing — not the raw CROC_SECRET=... croc ... command:

Receive file with code: "<phrase>"

Example:

Receive file with code: "character-send-1777681482"

The raw command leaks implementation detail the user doesn't need to read every time. The receiver agent/skill knows what to do with the code.

Step 3b — receiving

CROC_SECRET=<phrase> ~/.local/bin/croc --yes --overwrite --out <dest-dir>

Flag notes:

  • --yes — skip the interactive "save this file? (y/n)" prompt. Important for non-interactive use.
  • --overwrite — overwrite an existing file at the destination. Without it, croc refuses if the file already exists.
  • --out <dir> — directory to write into. Defaults to the current working directory.
  • The phrase goes through CROC_SECRET, not as a positional argument. (croc <phrase> worked in croc 9.x and is gone in 10.x without --classic.)

If the sender hasn't started yet and you start the receiver first, you'll see room (secure channel) not ready, maybe peer disconnected. Just retry once the sender is up. Order matters: when in doubt, sender first.

When you control both ends

If you're operating both the sender and the receiver (e.g., you have SSH to the other host, or you're driving two terminals via a multiplexer, or you're orchestrating two agents), the cadence is:

  1. Pick a code phrase.
  2. Start the sender. Confirm process is alive and the log shows Sending '<file>'.
  3. Start the receiver with the same phrase.
  4. Wait for completion. Both sides log progress.
  5. Read both logs to confirm and surface the path (peer-to-peer or relay).

If both sides start at the exact same instant they sometimes race and one bails with the "room not ready" error. Sender-first-then-receiver avoids it cleanly.

Reading the transfer path

After a successful transfer, look at these lines:

  • Sender: Sending (->X) — X is the other peer's address as the relay observed it.
  • Receiver: Receiving (<-X) — X is the sender's address as the relay observed it.

What X tells you:

X looks like Path Notes
100.x.x.x:port Tailscale CGNAT Direct over the Tailnet. No public-relay traffic.
192.168.x.x:port LAN Direct over the local network.
10.x.x.x:port LAN / VPN Direct on a private network.
Public IPv4 Public relay All bytes transited croc.schollz.com. Slower; uses volunteer infra.
127.0.0.1:port Sender's loopback to its own local relay Easy to misread. This does not mean both ends are on the same host. The receiver sees this when the sender's local relay reported back to itself over loopback. The actual TCP between hosts went over whatever path the sender's local relay was reachable on. To know the real hop, look at the sender's Sending (->X) line.

To force a particular path:

  • --relay host:port on both sides → use a private relay you run, instead of the default croc.schollz.com:9009-9013.
  • --no-local → skip local-network discovery, force public relay.
  • --only-local → mDNS/local discovery only, never use the relay (useful for air-gapped LANs).

Important caveats

WSL2 networking. Inside WSL2, the LAN-looking IPs (192.168.x.x) are on the VM's virtual NIC, not the Windows host's NIC. They're not reachable from other hosts on the user's actual LAN unless netsh interface portproxy is set up or Windows 11 mirrored networking is enabled. So a WSL2 sender on 192.168.1.134 will advertise that address, but the receiver's connection attempts will fail and the transfer falls back to the public relay. If both ends are on Tailscale (or another mesh VPN), use that — Tailscale runs inside WSL fine and gives a routable 100.x.x.x.

Code-phrase collisions. If a previous transfer with the same phrase didn't fully tear down (one side crashed), the relay may still think the room is "claimed." Use a fresh phrase rather than retrying the same one.

Public relay bandwidth. It's volunteer infrastructure run by the croc author out of pocket (~$25/month per croc#289). Don't push 100GB through it casually — for big files use a direct path (Tailscale, LAN, port-forwarded sender) or run your own relay with --relay.

Quick recipes

Send one file when you're at the terminal:

PHRASE=share-$(date +%s)
CROC_SECRET=$PHRASE ~/.local/bin/croc send /path/to/file

Then surface the phrase to the user as:

Receive file with code: "<phrase>"

Communicate it onward to the receiver however you'd normally — chat, copy/paste, etc.

Receive a file when you've been told the phrase:

CROC_SECRET=<phrase> ~/.local/bin/croc --yes --overwrite --out /tmp

Orchestrate both ends across SSH:

PHRASE=share-$(date +%s)
# Start sender on this host (background)
CROC_SECRET=$PHRASE nohup ~/.local/bin/croc send /path/to/file > /tmp/croc-send.log 2>&1 &
sleep 3
# Trigger receive on remote
ssh remote-host "CROC_SECRET=$PHRASE ~/.local/bin/croc --yes --overwrite --out /tmp"

Send a directory:

CROC_SECRET=$PHRASE ~/.local/bin/croc send --zip /path/to/dir

Coordinate with another agent in a tmux pane:

If your environment is two agents in tmux panes, you can relay the code phrase by sending a chat message to the other pane:

tmux send-keys -t <session>:<window>.<pane> -l "Receive: CROC_SECRET=$PHRASE ~/.local/bin/croc --yes --overwrite --out /tmp"
tmux send-keys -t <session>:<window>.<pane> Enter

But this is just one way to communicate the phrase. Slack, copy/paste, SMS, smoke signals, anything works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment