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

> Discover the no-mistakes IPC mechanism enabling CLI to daemon socket communication via an RPC layer using length-prefixed JSON over local domain sockets.

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-17

---

**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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/transport_windows.go))

Windows systems use **named pipes** instead of Unix sockets. The [`transport_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/client.go))

The CLI initializes communication through `ipc.NewClient()`, implemented in [`internal/ipc/client.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/server.go))

The daemon exposes functionality through `ipc.Server` in [`internal/ipc/server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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

```go
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

```go
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`](https://github.com/kunchenguid/no-mistakes/blob/main/transport_unix.go)) and Windows named pipes ([`transport_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/protocol.go).
- The **client** ([`client.go`](https://github.com/kunchenguid/no-mistakes/blob/main/client.go)) handles connection establishment in `NewClient()` and synchronous `Call()` invocations.
- The **server** ([`server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/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`](https://github.com/kunchenguid/no-mistakes/blob/main/transport_unix.go) and [`transport_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/transport_windows.go) to accommodate platform-specific path conventions.