Croc Reconnect Mechanism: How It Resumes Interrupted File Transfers Automatically

Croc automatically resumes interrupted transfers by creating a new reconnect room, applying exponential back-off delays, and cycling through remembered relay addresses to re-establish the PAKE handshake, supporting up to 10 retry attempts without requiring manual intervention.

The schollz/croc file transfer tool implements a robust croc reconnect mechanism that allows large file transfers to survive network interruptions without restarting from scratch. Located primarily in src/croc/croc.go, this protocol handles disconnection detection, state preservation, and secure re-handshaking automatically when connections drop.

Core Concepts of the Croc Reconnect Protocol

Version Negotiation and Compatibility

Before attempting any reconnection, croc validates that both sender and receiver support the reconnect protocol. The system defines a ReconnectVersion constant (set to 1 in src/croc/croc.go). If either peer reports an older version, the reconnect mechanism disables itself via canRetryTransfer to prevent protocol mismatches.

State Reset and Exponential Back-Off

When a transferDisconnectError occurs, croc initiates a controlled reset via resetForReconnectAttempt. This function clears file pointers, resets cryptographic key state, creates a fresh nextReconnectRoom for the retry attempt, and zeroes internal chunk counters.

The system then applies an exponential back-off strategy through reconnectBackoff(attempt), capping the delay at 5 seconds between retries. The default maxReconnectAttempts is set to 10.

Relay Candidate Selection

Croc maintains a memory of successful connections in reconnectRelayAddresses. During a retry, reconnectRelayCandidates builds an ordered list prioritizing the current control address first, followed by any previously remembered relay addresses. This ensures croc attempts the most likely working endpoints before falling back to alternatives.

PAKE Handshake Re-execution

Reconnection requires re-authentication. The sender invokes senderReconnectRelayAttempt, which internally calls senderWaitForHandshake to re-run the PAKE (Password Authenticated Key Exchange) protocol. The receiver uses receiverReconnectRelayAttempt, sending a single handshakeRequest byte to trigger the exchange. Once authenticated, transferWithReconnect loops back into the standard transfer routine.

Step-by-Step Reconnection Flow

When a network interruption occurs, croc executes the following sequence in src/croc/croc.go:

  1. Disconnection Detection: The core transfer() function returns a transferDisconnectError when the underlying TCP/relay connection drops.

  2. Retry Validation: canRetryTransfer checks three conditions: the reconnect version compatibility, existence of nextReconnectRoom, and whether the attempt counter remains below maxReconnectAttempts.

  3. Back-off Application: The system sleeps for the duration calculated by reconnectBackoff(attempt).

  4. State Reset: resetForReconnectAttempt clears per-file metadata while preserving the transfer progress (file offsets and chunk maps) stored in the client instance.

  5. Relay Dialing: reconnectRelayCandidates assembles the candidate list, and reconnectRelayAttempt dials each address sequentially until one accepts the connection.

  6. Secure Re-handshake: For senders, senderReconnectRelayAttempt runs the full PAKE handshake. Receivers trigger the handshake via receiverReconnectRelayAttempt.

  7. Transfer Resumption: Upon successful handshake, transferWithReconnect resumes the transfer() loop, continuing from the last acknowledged byte.

Practical Usage and Code Examples

Automatic Recovery via CLI

The reconnect mechanism requires no special flags. If a transfer interrupts, simply rerun the original command:


# Terminal 1 - Sender

croc send large-file.iso

# Terminal 2 - Receiver  

croc receive

Croc detects the previous session state, creates the reconnect room automatically, and resumes the transfer.

Debug Output

Enable --debug to observe the reconnect logic:

croc --debug send file.txt

Example output during reconnection:

debug: reconnect attempt 1 after 100ms
debug: resetting transfer state for reconnect attempt 1
debug: could not reconnect to any relay: 127.0.0.1:9009: connection refused

Go API Implementation

Developers using the Go library get automatic reconnect for free:

c, _ := croc.New(croc.Options{
    SharedSecret: "secret-code",
    IsSender:     true,
})

// Transfer automatically handles disconnections internally
err := c.Send(filesInfo, emptyFolders, totalFolders)
if err != nil {
    log.Fatal("Transfer failed after retries:", err)
}

The Client struct internally invokes transferWithReconnect, wrapping all retry logic transparently.

Key Source Files Implementing Reconnect

  • src/croc/croc.go: Contains ReconnectVersion, maxReconnectAttempts, transferWithReconnect, resetForReconnectAttempt, and reconnectBackoff.
  • src/croc/ctx.go: Provides cancellable context (c.stop) for clean aborts during retries.
  • src/tcp/tcp.go: Low-level TCP wrapper (ConnectToTCPServer) used by reconnection attempts.
  • src/comm/comm.go: Connection abstraction allowing the reconnect routine to swap underlying Comm objects while preserving client state.
  • src/message/message.go: Defines PAKE handshake messages re-exchanged during senderReconnectRelayAttempt and receiverReconnectRelayAttempt.

Summary

  • Croc's reconnect mechanism operates entirely within src/croc/croc.go, requiring no manual user intervention.
  • The system supports up to 10 retry attempts with exponential back-off (max 5-second delay).
  • State preservation ensures file transfers resume from the exact byte offset where interruption occurred.
  • Relay candidate caching (reconnectRelayAddresses) prioritizes known-good endpoints during reconnection sweeps.
  • Version checking via ReconnectVersion prevents incompatible peers from attempting reconnection.

Frequently Asked Questions

How many times will croc retry a failed connection?

By default, croc attempts to reconnect 10 times before giving up. This limit is defined by the maxReconnectAttempts constant in src/croc/croc.go. Each retry applies an exponential back-off delay calculated by reconnectBackoff, capped at 5 seconds between attempts.

Do I need special flags to enable reconnect?

No. The croc reconnect mechanism is automatic and transparent. Simply rerun the same croc send or croc receive command after a failure. The tool detects the previous session through the shared secret, creates a nextReconnectRoom, and attempts to resume without additional configuration.

What happens if the protocol version mismatches during reconnect?

If canRetryTransfer detects that either peer reports a ReconnectVersion older than the current protocol version (version 1), reconnection is immediately disabled. This prevents state corruption and forces a fresh transfer rather than attempting incompatible reconnection logic.

How does croc know where to resume the file transfer?

Before executing resetForReconnectAttempt, croc preserves the transfer progress in the Client struct, including file offsets and chunk acknowledgment maps. After successfully re-handshaking via senderReconnectRelayAttempt or receiverReconnectRelayAttempt, the transfer function resumes reading from these preserved offsets, ensuring no data duplication or loss.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →