Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/core/mesh-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ export class MeshStore implements CommsStore {
);
connected = true;
} catch {
let eaddrinuseMessage: string | undefined;
try {
await this.requireTransport().becomeCoordinator(
COORDINATOR_HOST,
Expand All @@ -354,18 +355,21 @@ export class MeshStore implements CommsStore {
if (!msg.includes("EADDRINUSE")) {
throw coordErr;
}
// EADDRINUSE — port held by an unresponsive process. Degrade.
// EADDRINUSE — connectToCoordinator already failed above, so whatever holds this port never answered as a reachable coordinator either. Degrade, naming the actual mismatch rather than a generic "unavailable".
eaddrinuseMessage = msg;
}
}

if (!connected) {
this.events.onError?.(
new Error(
`MeshStore: could not join or create mesh on port ${String(this.coordinatorPort)}. ` +
"Running without mesh — agent-comms will be unavailable.",
),
);
return;
if (!connected) {
this.events.onError?.(
new Error(
`MeshStore: could not join or create a mesh on port ${String(this.coordinatorPort)}. ` +
`port ${String(this.coordinatorPort)} is already in use by something that never answered as a reachable coordinator -- a stale process from a previous run, or an incompatible agent-comms version. ` +
"agent-comms will run without mesh connectivity until this is resolved. " +
`(${eaddrinuseMessage ?? "unknown reason"})`,
),
);
return;
}
}

this.requireTransport().unref();
Expand Down
13 changes: 8 additions & 5 deletions src/test/mesh-store-orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,15 @@ describe("MeshStore — init()", () => {
await expect(store.init()).resolves.toBeUndefined();

expect(onError).toHaveBeenCalledTimes(1);
expect(onError.mock.calls[0]?.[0]?.message).toContain(
"could not join or create mesh",
);
expect(onError.mock.calls[0]?.[0]?.message).toContain(
"Running without mesh — agent-comms will be unavailable.",
const message = onError.mock.calls[0]?.[0]?.message ?? "";
expect(message).toContain("could not join or create a mesh");
// The message must name the actual mismatch, not just say the mesh is unavailable: something already holds the port but never answered as a reachable coordinator (connectToCoordinator already failed first), which is a stale process or an incompatible agent-comms version -- not a generic catch-all.
expect(message).toContain(
"port 19876 is already in use by something that never answered as a reachable coordinator",
);
expect(message).toContain("stale process");
expect(message).toContain("incompatible agent-comms version");
expect(message).toContain("listen EADDRINUSE: address already in use");
expect(transport.unref).not.toHaveBeenCalled();
});

Expand Down