No-Mistakes IPC Mechanism: How the CLI Communicates with the Daemon Socket

The kunchenguid/no-mistakes CLI communicates with its background daemon through a lightweight RPC layer built on local domain sockets, using a length-prefixed JSON protocol that abstracts transport differences between Unix sockets and Windows named pipes.

The repository implements a custom inter-process communication (IPC) layer in the internal/ipc package. This mechanism enables synchronous request-response cycles between the command-line client and the persistent daemon process while maintaining cross-platform compatibility.

Cross-Platform Transport Abstraction

The IPC layer isolates platform-specific networking code behind a common interface. According to the no-mistakes source code, the transport implementation splits into OS-specific files that both satisfy the standard net.Conn interface.

Unix Domain Sockets (internal/ipc/transport_unix.go)

On Linux and macOS, the daemon listens on a Unix domain socket located at $NM_HOME/daemon.sock or within the system temporary directory. The transport_unix.go file handles the net.Dial("unix", path) operations for the client and the corresponding net.Listen("unix", path) call for the server.

Windows Named Pipes (internal/ipc/transport_windows.go)

Windows systems use named pipes instead of Unix sockets. The transport_windows.go implementation connects to \\.\pipe\no-mistakes, providing the same net.Conn semantics to the higher-level RPC code through Windows-specific API bindings.

JSON-RPC Protocol and Message Framing

All messages exchanged between the CLI and daemon follow the lightweight protocol defined in internal/ipc/protocol.go. Instead of heavy serialization frameworks, the project uses length-prefixed JSON encoding.

Each RPC message consists of a 4-byte little-endian integer header specifying the payload length, followed by the JSON-encoded frame. The protocol.go file defines the Request and Response structs:

  • Request contains method and params fields.
  • Response contains either a result or an error field.

This framing ensures the receiver can distinguish complete messages from partial socket reads, preventing message boundary corruption.

Client-Side Socket Communication (internal/ipc/client.go)

The CLI initializes communication through ipc.NewClient(), implemented in internal/ipc/client.go. This constructor resolves the socket path and establishes the connection through a three-step process:

  1. Path Resolution: ipc.SocketPath() returns filepath.Join(os.TempDir(), "no-mistakes.sock") on Unix or the named pipe identifier on Windows.
  2. Transport Dialing: The dial() method selects the appropriate transport—net.Dial for Unix or windowsDial for Windows—based on runtime.GOOS.
  3. RPC Invocation: The Call(method, params, response) method marshals a protocol.Request, writes the length-prefixed frame to the socket, and blocks awaiting a protocol.Response unmarshaled into the provided response pointer.

Daemon Server Implementation (internal/ipc/server.go)

The daemon exposes functionality through ipc.Server in internal/ipc/server.go. The server listens on the same socket path used by the client, then enters an accept loop that handles each connection concurrently.

When a connection arrives, the server:

  • Reads the 4-byte length header.
  • Reads the subsequent JSON payload into a protocol.Request.
  • Dispatches to registered handlers via s.Handle(method, handler).
  • Marshals the return value into a protocol.Response and writes it back with the same length-prefix framing.

The server exits cleanly when encountering net.ErrClosed during shutdown.

Practical Code Examples

CLI Side: Querying Daemon Status

package main

import (
    "log"
    "github.com/kunchenguid/no-mistakes/internal/ipc"
)

func main() {
    // Create a client that knows how to talk to the daemon.
    c, err := ipc.NewClient()
    if err != nil {
        log.Fatalf("cannot create IPC client: %v", err)
    }
    defer c.Close()

    // Perform an RPC call; the daemon implements the "status" method.
    var status ipc.StatusResponse
    if err := c.Call("status", nil, &status); err != nil {
        log.Fatalf("daemon error: %v", err)
    }

    log.Printf("daemon version: %s, running: %t", status.Version, status.Running)
}

Daemon Side: Registering the Status Handler

package daemon

import (
    "github.com/kunchenguid/no-mistakes/internal/ipc"
)

func registerHandlers(s *ipc.Server) {
    s.Handle("status", func(_ *ipc.Request) (*ipc.Response, error) {
        // Gather whatever information the CLI cares about.
        resp := ipc.StatusResponse{
            Version: "v1.2.3",
            Running: true,
        }
        return ipc.NewResponse(resp), nil
    })
}

Summary

  • The IPC mechanism uses a custom RPC layer over local sockets to connect the no-mistakes CLI with its background daemon.
  • Cross-platform transport abstracts Unix domain sockets (transport_unix.go) and Windows named pipes (transport_windows.go) behind a common net.Conn interface.
  • Message framing relies on a 4-byte little-endian length prefix followed by JSON payloads defined in protocol.go.
  • The client (client.go) handles connection establishment in NewClient() and synchronous Call() invocations.
  • The server (server.go) accepts connections, demultiplexes requests to registered handlers, and returns structured responses with automatic framing.

Frequently Asked Questions

What type of socket does no-mistakes use for IPC?

The project uses Unix domain sockets on Linux and macOS, typically located at $NM_HOME/daemon.sock or in the system temporary directory. On Windows, it uses named pipes with the path \\.\pipe\no-mistakes. Both transports implement the standard net.Conn interface, allowing the RPC layer to remain platform-agnostic.

How does the IPC protocol prevent message corruption?

The protocol implements length-prefixed framing where each JSON message is preceded by a 4-byte little-endian integer indicating the payload size. This ensures the receiver can read complete messages even when the underlying stream delivers partial data or coalesces multiple sends into a single read operation.

Can multiple CLI instances talk to the same daemon simultaneously?

Yes. The ipc.Server implementation in internal/ipc/server.go accepts concurrent connections and handles requests in separate goroutines. The socket transport supports multiple simultaneous clients, though specific method handlers may implement their own synchronization if they access shared daemon state.

Where is the socket path configured in the source code?

The ipc.SocketPath() function determines the socket location dynamically. On Unix systems, it returns filepath.Join(os.TempDir(), "no-mistakes.sock") or respects the $NM_HOME environment variable. On Windows, it returns the named pipe identifier. This logic is split between transport_unix.go and transport_windows.go to accommodate platform-specific path conventions.

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 →