From fb17a322832edbfa7c7477279f4c949c0176ab34 Mon Sep 17 00:00:00 2001
From: Emanuel Stadler <9994339+emanuelst@users.noreply.github.com>
Date: Tue, 6 Jan 2026 21:17:55 +0100
Subject: [PATCH 1/3] feat: enhance error handling for socket connection errors
- Added `isError` property to `EmbeddedPiRunResult` and reply items to indicate error states.
- Updated error handling in `runReplyAgent` to provide more informative messages for specific socket connection errors.
---
src/agents/pi-embedded-runner.ts | 11 +++++++++--
src/auto-reply/reply/agent-runner.ts | 21 +++++++++++++++++++--
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/src/agents/pi-embedded-runner.ts b/src/agents/pi-embedded-runner.ts
index 813a4ba0d..78e895c24 100644
--- a/src/agents/pi-embedded-runner.ts
+++ b/src/agents/pi-embedded-runner.ts
@@ -99,6 +99,7 @@ export type EmbeddedPiRunResult = {
mediaUrl?: string;
mediaUrls?: string[];
replyToId?: string;
+ isError?: boolean;
}>;
meta: EmbeddedPiRunMeta;
};
@@ -1009,12 +1010,17 @@ export async function runEmbeddedPiAgent(params: {
usage,
};
- const replyItems: Array<{ text: string; media?: string[] }> = [];
+ const replyItems: Array<{
+ text: string;
+ media?: string[];
+ isError?: boolean;
+ }> = [];
const errorText = lastAssistant
? formatAssistantErrorText(lastAssistant)
: undefined;
- if (errorText) replyItems.push({ text: errorText });
+
+ if (errorText) replyItems.push({ text: errorText, isError: true });
const inlineToolResults =
params.verboseLevel === "on" &&
@@ -1047,6 +1053,7 @@ export async function runEmbeddedPiAgent(params: {
text: item.text?.trim() ? item.text.trim() : undefined,
mediaUrls: item.media?.length ? item.media : undefined,
mediaUrl: item.media?.[0],
+ isError: item.isError,
}))
.filter(
(p) =>
diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts
index d4e7ba652..d25aac8e6 100644
--- a/src/auto-reply/reply/agent-runner.ts
+++ b/src/auto-reply/reply/agent-runner.ts
@@ -401,8 +401,25 @@ export async function runReplyAgent(params: {
const sanitizedPayloads = isHeartbeat
? payloadArray
: payloadArray.flatMap((payload) => {
- const text = payload.text;
- if (!text || !text.includes("HEARTBEAT_OK")) return [payload];
+ let text = payload.text;
+
+ if (payload.isError) {
+ // Handle Bun fetch socket connection error that may indicate a context length issue
+ // Error source: https://github.com/oven-sh/bun/blob/main/src/bun.js/webcore/fetch/FetchTasklet.zig
+ const isBunFetchSocketError =
+ text ===
+ "The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()";
+
+ if (isBunFetchSocketError) {
+ text = `⚠️ LLM connection failed. This could be due to server issues, network problems, or context length exceeded (e.g., with local LLMs like LM Studio). Original error:
+ \`\`\`
+ ${text || "Unknown error"}
+ \`\`\``;
+ }
+ }
+
+ if (!text || !text.includes("HEARTBEAT_OK"))
+ return [{ ...payload, text }];
const stripped = stripHeartbeatToken(text, { mode: "message" });
if (stripped.didStrip && !didLogHeartbeatStrip) {
didLogHeartbeatStrip = true;
From a1f5cfcd08c8bc0439e1f7e70e227f8521070fbc Mon Sep 17 00:00:00 2001
From: Peter Steinberger
Date: Tue, 6 Jan 2026 22:26:42 +0100
Subject: [PATCH 2/3] docs: refresh clawtributors
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 35500d4b2..848666e45 100644
--- a/README.md
+++ b/README.md
@@ -453,4 +453,5 @@ Thanks to all clawtributors:
+
From 96164b5955c913657e9730ffddd6a4eb51919471 Mon Sep 17 00:00:00 2001
From: Peter Steinberger
Date: Tue, 6 Jan 2026 22:43:29 +0100
Subject: [PATCH 3/3] fix: improve socket error handling
---
CHANGELOG.md | 1 +
.../agent-runner.heartbeat-typing.test.ts | 22 ++++++++++++++
src/auto-reply/reply/agent-runner.ts | 30 +++++++++++--------
src/auto-reply/types.ts | 1 +
4 files changed, 41 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c9e7b3e9..17c9080de 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@
- Telegram: stop typing after tool results. Thanks @AbhisekBasu1 for PR #322.
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.
- Auto-reply: require slash for control commands to avoid false triggers in normal text.
+- Auto-reply: flag error payloads and improve Bun socket error messaging. Thanks @emanuelst for PR #331.
- Commands: unify native + text chat commands behind `commands.*` config (Discord/Slack/Telegram). Thanks @thewilloftheshadow for PR #275.
- Auto-reply: treat steer during compaction as a follow-up, queued until compaction completes.
- Auth: lock auth profile refreshes to avoid multi-instance OAuth logouts; keep credentials on refresh failure.
diff --git a/src/auto-reply/reply/agent-runner.heartbeat-typing.test.ts b/src/auto-reply/reply/agent-runner.heartbeat-typing.test.ts
index 85eb5cf84..017a51ca9 100644
--- a/src/auto-reply/reply/agent-runner.heartbeat-typing.test.ts
+++ b/src/auto-reply/reply/agent-runner.heartbeat-typing.test.ts
@@ -209,4 +209,26 @@ describe("runReplyAgent typing (heartbeat)", () => {
expect(payloads[0]?.text).toContain("count 1");
expect(sessionStore.main.compactionCount).toBe(1);
});
+
+ it("rewrites Bun socket errors into friendly text", async () => {
+ runEmbeddedPiAgentMock.mockImplementationOnce(async () => ({
+ payloads: [
+ {
+ text: "TypeError: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()",
+ isError: true,
+ },
+ ],
+ meta: {},
+ }));
+
+ const { run } = createMinimalRun();
+ const res = await run();
+ const payloads = Array.isArray(res) ? res : res ? [res] : [];
+ expect(payloads.length).toBe(1);
+ expect(payloads[0]?.text).toContain("LLM connection failed");
+ expect(payloads[0]?.text).toContain(
+ "socket connection was closed unexpectedly",
+ );
+ expect(payloads[0]?.text).toContain("```");
+ });
});
diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts
index d25aac8e6..79b85e57a 100644
--- a/src/auto-reply/reply/agent-runner.ts
+++ b/src/auto-reply/reply/agent-runner.ts
@@ -31,6 +31,21 @@ import { extractReplyToTag } from "./reply-tags.js";
import { incrementCompactionCount } from "./session-updates.js";
import type { TypingController } from "./typing.js";
+const BUN_FETCH_SOCKET_ERROR_RE = /socket connection was closed unexpectedly/i;
+
+const isBunFetchSocketError = (message?: string) =>
+ Boolean(message && BUN_FETCH_SOCKET_ERROR_RE.test(message));
+
+const formatBunFetchSocketError = (message: string) => {
+ const trimmed = message.trim();
+ return [
+ "⚠️ LLM connection failed. This could be due to server issues, network problems, or context length exceeded (e.g., with local LLMs like LM Studio). Original error:",
+ "```",
+ trimmed || "Unknown error",
+ "```",
+ ].join("\n");
+};
+
export async function runReplyAgent(params: {
commandBody: string;
followupRun: FollowupRun;
@@ -403,19 +418,8 @@ export async function runReplyAgent(params: {
: payloadArray.flatMap((payload) => {
let text = payload.text;
- if (payload.isError) {
- // Handle Bun fetch socket connection error that may indicate a context length issue
- // Error source: https://github.com/oven-sh/bun/blob/main/src/bun.js/webcore/fetch/FetchTasklet.zig
- const isBunFetchSocketError =
- text ===
- "The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()";
-
- if (isBunFetchSocketError) {
- text = `⚠️ LLM connection failed. This could be due to server issues, network problems, or context length exceeded (e.g., with local LLMs like LM Studio). Original error:
- \`\`\`
- ${text || "Unknown error"}
- \`\`\``;
- }
+ if (payload.isError && text && isBunFetchSocketError(text)) {
+ text = formatBunFetchSocketError(text);
}
if (!text || !text.includes("HEARTBEAT_OK"))
diff --git a/src/auto-reply/types.ts b/src/auto-reply/types.ts
index 62b6d75bb..b76a0a5a1 100644
--- a/src/auto-reply/types.ts
+++ b/src/auto-reply/types.ts
@@ -14,4 +14,5 @@ export type ReplyPayload = {
mediaUrl?: string;
mediaUrls?: string[];
replyToId?: string;
+ isError?: boolean;
};