How the IPC Layer Handles Communication and Health Checks with the Daemon
The internal/ipc package implements a JSON-RPC layer over Unix-domain sockets or Windows named pipes, using configurable dial timeouts, mutex-protected request serialization, and dedicated connections for event streaming to enable reliable CLI-to-daemon communication and sub-second health checks.
The no-mistakes repository relies on a lightweight inter-process communication (IPC) mechanism to coordinate between the CLI client and a background daemon process. Understanding how the IPC layer handles communication and health checks with the daemon is essential for debugging connection failures and optimizing timeout behavior in production environments.
Connection Establishment and Dial Timeouts
Configuring Connection Timeouts
In internal/ipc/client.go, the Dial function establishes a transport connection via dialEndpoint, which creates either a Unix-domain socket or a Windows named pipe. The operation respects a configurable connectTimeout that first checks the NM_DAEMON_CONNECT_TIMEOUT environment variable, then falls back to config.DefaultDaemonConnectTimeout defined in internal/config/config.go. If the dial operation exceeds this threshold, the function returns a ConnectTimeoutError that implements the standard Timeout() method, allowing callers to distinguish between network failures and bounded-connect timeouts.
Fast Health-Check Timeouts
For liveness probes that require immediate feedback, the package exposes client.DefaultDialTimeout set to 250 ms. This short deadline is applied as a read timeout during health checks in internal/daemon/selfexec.go, ensuring the CLI can quickly detect an unresponsive daemon and spawn a new instance if necessary.
JSON-RPC Request/Response Cycle
Serialized Request Processing with Mutex Protection
The Client struct maintains a single net.Conn, a JSON encoder, a line-oriented scanner, and a mutex (mu) that guarantees only one request is in flight at a time. The Call(method string, params interface{}, result interface{}) and CallWithTimeout(method string, params interface{}, result interface{}, timeout time.Duration) methods acquire this lock before writing to the socket. This prevents interleaving of request/response pairs on the same connection, ensuring reliable request-reply semantics even under concurrent access.
Timeout Handling and Error Propagation
Each RPC builds a request using NewRequest, encodes it to the socket, then sets a read deadline based on defaultCallTimeout (30 seconds unless overridden). The response is read line-by-line from the scanner, unmarshaled into a Response struct defined in internal/ipc/protocol.go, and any JSON-RPC error is propagated as an *RPCError. This structured error handling allows the CLI to distinguish between transport failures and application-level RPC errors.
Real-Time Event Streaming
For long-running notifications such as progress events during a run, the client uses the Subscribe(socket string, params *SubscribeParams) (<-chan Event, func(), error) function. Unlike standard RPC calls, this opens a dedicated connection separate from the main client channel, sends a MethodSubscribe request defined in internal/ipc/protocol.go, and validates the initial response.
A background goroutine reads subsequent lines from the socket, unmarshals them into Event structs, and forwards them on a buffered channel. The returned cancel function safely shuts down the scanner, closes the socket, and uses sync.Once to guarantee the event channel is closed exactly once, preventing panics from double-close operations.
// Subscribe to run‑level events.
func watchRunEvents(socket string, runID string) (<-chan ipc.Event, func()) {
params := &ipc.SubscribeParams{RunID: runID}
ch, cancel, _ := ipc.Subscribe(socket, params)
return ch, cancel
}
Health Check Implementation
The daemon’s health endpoint in internal/daemon/selfexec.go performs a minimal liveness check by dialing the IPC socket with DefaultDialTimeout. If the connection succeeds and a basic ping RPC replies within the 250 ms deadline, the daemon is considered healthy. Because Dial respects the configurable connect timeout, health checks fail fast when the daemon is not listening or the socket is stale, triggering the CLI's auto-restart logic.
// Simple health check – returns nil if the daemon is reachable.
func daemonHealthy(socket string) error {
// Use the short default dial timeout for a quick probe.
c, err := ipc.Dial(socket)
if err != nil {
return err
}
defer c.Close()
// Perform a lightweight RPC, e.g., "ping".
var pong string
return c.CallWithTimeout(ipc.MethodPing, nil, &pong, ipc.DefaultDialTimeout)
}
Summary
- The
internal/ipcpackage provides a JSON-RPC transport over Unix-domain sockets or Windows named pipes, with configurable timeouts viaNM_DAEMON_CONNECT_TIMEOUTandconfig.DefaultDaemonConnectTimeout. - A mutex-protected
Clientininternal/ipc/client.goensures request-response serialization, preventing interleaving on shared connections. - Health checks leverage a 250 ms
DefaultDialTimeoutfor fast daemon liveness detection ininternal/daemon/selfexec.go. - The
SubscribeAPI uses dedicated connections andsync.Oncefor safe teardown of long-running event streams. - All timeout errors implement standard interfaces (
Timeout()) to allow callers to distinguish between network and application failures.
Frequently Asked Questions
How does the IPC layer prevent request interleaving on shared connections?
The Client struct in internal/ipc/client.go protects the underlying net.Conn with a mutex (mu). Both Call and CallWithTimeout acquire this lock before writing to the socket, ensuring that only one request-response pair is in flight at a time. This serialization guarantees that JSON-RPC responses are correctly matched to their requests even when multiple goroutines attempt to use the same client concurrently.
What timeout values does the No-Mistakes IPC layer use for health checks?
Health checks use DefaultDialTimeout (250 ms) as defined in internal/ipc/client.go. This value is applied as a read deadline when dialing the socket in internal/daemon/selfexec.go. For general RPC calls, the default is defaultCallTimeout (30 s), while the initial connection timeout is controlled by the NM_DAEMON_CONNECT_TIMEOUT environment variable or falls back to config.DefaultDaemonConnectTimeout.
How does the subscription API handle connection shutdown safely?
The Subscribe function returns a cancel closure that uses sync.Once to ensure the event channel is closed exactly once, even if invoked multiple times. This prevents panics from double-close operations and guarantees that the dedicated connection underlying the subscription is properly terminated, releasing system resources and preventing goroutine leaks.
What is the difference between ConnectTimeoutError and RPCError?
ConnectTimeoutError is returned by Dial when the initial socket connection exceeds the configured timeout, implementing the Timeout() method for error classification. In contrast, *RPCError represents application-level JSON-RPC errors returned by the daemon after a successful connection, such as invalid method names or malformed parameters, and is decoded from the JSON response body defined in internal/ipc/protocol.go.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →