# IPC Communication Method Between CLI and Daemon Socket in no-mistakes

> Discover how the no-mistakes CLI uses Unix domain sockets to communicate with its daemon via RPC and JSON messages. Understand the IPC communication method.

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

---

**The no-mistakes CLI communicates with its background daemon through a lightweight RPC protocol over Unix domain sockets (or Windows named pipes), using length-prefixed JSON messages for request-response cycles.**

The `no-mistakes` project implements a custom inter-process communication (IPC) layer that enables the command-line interface to control a persistent background daemon. This IPC communication method abstracts transport details so the same RPC protocol operates seamlessly across Linux, macOS, and Windows environments.

## How the IPC Architecture Works

The communication stack follows a client-server model where the CLI acts as the client and the daemon hosts the server. The implementation resides in the `internal/ipc` package, split across platform-specific transport layers and shared protocol logic.

### Socket Path Resolution

When the CLI initializes, it invokes `ipc.NewClient()` to establish connectivity. The client queries the transport layer for the socket address via `ipc.SocketPath()`, which resolves to platform-specific locations:

- **Unix systems**: `filepath.Join(os.TempDir(), "no-mistakes.sock")` or `$NM_HOME/daemon.sock`
- **Windows**: Named pipe at `\\.\pipe\no-mistakes`

### Connection Establishment

The client dials the resolved address using platform-appropriate system calls. In [`internal/ipc/client.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/client.go), the connection logic selects the transport implementation based on `runtime.GOOS`:

```go
func (c *Client) dial() (net.Conn, error) {
    if runtime.GOOS == "windows" {
        return windowsDial(ipc.SocketPath())
    }
    return net.Dial("unix", ipc.SocketPath())
}

```

On Unix, this uses standard `net.Dial` with the `"unix"` network type, while Windows utilizes named pipe APIs via `winproc.DialPipe`.

### Message Framing Protocol

All RPC messages serialize to JSON and prepend a 4-byte little-endian integer indicating payload length. This framing mechanism, defined in [`internal/ipc/protocol.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/protocol.go), ensures the receiver can delineate complete messages from stream data even when the underlying socket delivers partial buffers.

The wire format consists of:
- **4 bytes**: Payload length (uint32, little-endian)
- **N bytes**: JSON-encoded `Request` or `Response` struct containing `method`, `params`, and `result` fields

### Request-Response Cycle

The IPC communication method follows a synchronous call pattern:

1. The CLI invokes `client.Call(method, params, &result)`
2. The client marshals a `protocol.Request` struct, applies length-prefix framing, and writes to the socket
3. The daemon's server routine (in [`internal/ipc/server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/server.go)) accepts the connection, reads the frame, and unmarshals the request
4. The server dispatches to the registered handler for the specified `method`
5. The handler returns a `protocol.Response` containing either a result payload or an error
6. The client unmarshals the response into the provided result pointer

## Cross-Platform Transport Abstraction

The repository isolates platform-specific socket implementations to maximize code reuse:

- **[`internal/ipc/transport_unix.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/transport_unix.go)**: Implements `net.Conn` interface over Unix domain sockets located in the daemon's home directory, restricting access to the current user
- **[`internal/ipc/transport_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/transport_windows.go)**: Provides identical semantics using Windows named pipes with the same logical naming convention

This abstraction allows the RPC layer in [`client.go`](https://github.com/kunchenguid/no-mistakes/blob/main/client.go) and [`server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/server.go) to remain platform-agnostic while leveraging native IPC mechanisms optimized for each operating system.

## Implementation Examples

### CLI Side: Sending a Status Request

The following code from the CLI demonstrates initializing the IPC client and invoking the `status` method:

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

The daemon registers method handlers using `server.Handle()`:

```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
    })
}

```

## Key Source Files

The IPC communication method relies on these components:

- **[`internal/ipc/client.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/client.go)**: Implements `NewClient()`, `Call()`, and connection management for the CLI side
- **[`internal/ipc/server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/server.go)**: Handles socket listening, connection acceptance, and request dispatching in the daemon
- **[`internal/ipc/protocol.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/protocol.go)**: Defines `Request` and `Response` structs along with length-prefix framing logic
- **[`internal/ipc/transport_unix.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/transport_unix.go)**: Unix-domain socket implementation details
- **[`internal/ipc/transport_windows.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/transport_windows.go)**: Windows named pipe implementation details
- **[`internal/daemon/manager.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/manager.go)**: Boots the IPC server when the daemon initializes

## Summary

- The **IPC communication method** uses a custom RPC protocol over local sockets, avoiding heavy external dependencies
- **Length-prefixed JSON framing** ensures reliable message boundaries across stream-based transports
- **Platform abstraction** via [`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) enables single-binary cross-platform support
- **Synchronous request-response** pattern keeps CLI logic straightforward while the daemon handles asynchronous workloads
- **Security** derives from filesystem permissions on Unix sockets (located in `$NM_HOME`) and named pipe ACLs on Windows

## Frequently Asked Questions

### What transport protocols does no-mistakes use for IPC?

On Linux and macOS, the system uses **Unix domain sockets** created in either the system temporary directory or the daemon's home directory. On Windows, it uses **named pipes** with the identifier `\\.\pipe\no-mistakes`. Both transports implement the standard `net.Conn` interface, allowing the RPC layer to remain identical across platforms.

### How does the daemon handle multiple concurrent CLI requests?

The server implementation in [`internal/ipc/server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/ipc/server.go) accepts connections in a loop, spawning goroutines to handle each client connection independently. This allows multiple CLI instances to communicate with the daemon simultaneously without blocking the main server thread.

### Is the IPC communication secure?

Yes, by leveraging filesystem-level security. Unix domain sockets are created within the daemon's private `$NM_HOME` directory with restricted permissions, ensuring only the owning user can connect. Windows named pipes inherit the default security descriptor, which restricts access to the same user session running the daemon.

### What happens if the daemon socket is deleted while running?

If the socket file is deleted externally while the daemon operates, existing connections remain active because they operate on file descriptors already established. However, new CLI instances cannot connect until the daemon restarts and recreates the socket. The daemon's `Serve()` loop in [`server.go`](https://github.com/kunchenguid/no-mistakes/blob/main/server.go) monitors for `net.ErrClosed` to detect shutdown conditions gracefully.