refactor: dedupe hook gateway error formatting

This commit is contained in:
Peter Steinberger
2026-04-07 01:46:37 +01:00
parent 474db91bed
commit 3d23103081
16 changed files with 43 additions and 38 deletions

View File

@@ -1,6 +1,7 @@
import { resolveAgentTimeoutMs } from "../../agents/timeout.js";
import type { OpenClawConfig } from "../../config/config.js";
import { logVerbose } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import { isAcpSessionKey } from "../../sessions/session-key-utils.js";
import {
@@ -1291,7 +1292,7 @@ export class AcpSessionManager {
});
} catch (recoveryError) {
logVerbose(
`acp close recovery: unable to prepare fresh session for ${sessionKey}: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`,
`acp close recovery: unable to prepare fresh session for ${sessionKey}: ${formatErrorMessage(recoveryError)}`,
);
}
}
@@ -1665,7 +1666,7 @@ export class AcpSessionManager {
});
} catch (error) {
logVerbose(
`acp-manager: failed preparing a fresh persistent session for ${params.sessionKey}: ${error instanceof Error ? error.message : String(error)}`,
`acp-manager: failed preparing a fresh persistent session for ${params.sessionKey}: ${formatErrorMessage(error)}`,
);
return false;
}

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "../config/config.js";
import type { SessionAcpMeta } from "../config/sessions/types.js";
import { logVerbose } from "../globals.js";
import { formatErrorMessage } from "../infra/errors.js";
import { getAcpSessionManager } from "./control-plane/manager.js";
import { resolveConfiguredAcpBindingSpecBySessionKey } from "./persistent-bindings.resolve.js";
import {
@@ -98,7 +99,7 @@ export async function ensureConfiguredAcpBindingSession(params: {
sessionKey,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = formatErrorMessage(error);
logVerbose(
`acp-configured-binding: failed ensuring ${params.spec.channel}:${params.spec.accountId}:${params.spec.conversationId} -> ${sessionKey}: ${message}`,
);
@@ -192,7 +193,7 @@ export async function resetAcpSessionInPlace(params: {
// on the next turn through the normal binding readiness path.
return { ok: true };
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = formatErrorMessage(error);
logVerbose(`acp-configured-binding: failed reset for ${sessionKey}: ${message}`);
return {
ok: false,

View File

@@ -7,6 +7,7 @@ import JSON5 from "json5";
import { ensureOwnerDisplaySecret } from "../agents/owner-display.js";
import { applyRuntimeLegacyConfigMigrations } from "../commands/doctor/shared/runtime-compat-api.js";
import { loadDotEnv } from "../infra/dotenv.js";
import { formatErrorMessage } from "../infra/errors.js";
import { resolveRequiredHomeDir } from "../infra/home-dir.js";
import {
loadShellEnvFallback,
@@ -2550,7 +2551,7 @@ export async function writeConfigFile(
} catch {
// Keep the original refresh failure as the surfaced error.
}
const detail = error instanceof Error ? error.message : String(error);
const detail = formatErrorMessage(error);
throw new ConfigRuntimeRefreshError(
`Config was written to ${io.configPath}, but runtime snapshot refresh failed: ${detail}`,
{ cause: error },

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { CURRENT_SESSION_VERSION, SessionManager } from "@mariozechner/pi-coding-agent";
import { formatErrorMessage } from "../../infra/errors.js";
import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
import {
resolveDefaultSessionStorePath,
@@ -184,7 +185,7 @@ export async function appendExactAssistantMessageToSessionTranscript(params: {
} catch (err) {
return {
ok: false,
reason: err instanceof Error ? err.message : String(err),
reason: formatErrorMessage(err),
};
}

View File

@@ -13,6 +13,7 @@ import {
import { resolveStorePath } from "../config/sessions/paths.js";
import { loadSessionStore, updateSessionStore } from "../config/sessions/store.js";
import type { SessionEntry } from "../config/sessions/types.js";
import { formatErrorMessage } from "../infra/errors.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { type RuntimeEnv, defaultRuntime } from "../runtime.js";
@@ -131,7 +132,7 @@ async function restoreMainSessionMapping(
);
return undefined;
} catch (err) {
return err instanceof Error ? err.message : String(err);
return formatErrorMessage(err);
}
}
@@ -150,7 +151,7 @@ export async function runBootOnce(params: {
try {
result = await loadBootFile(params.workspaceDir);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
const message = formatErrorMessage(err);
log.error(`boot: failed to read ${BOOT_FILENAME}: ${message}`);
return { status: "failed", reason: message };
}
@@ -183,7 +184,7 @@ export async function runBootOnce(params: {
params.deps,
);
} catch (err) {
agentFailure = err instanceof Error ? err.message : String(err);
agentFailure = formatErrorMessage(err);
log.error(`boot: agent run failed: ${agentFailure}`);
}

View File

@@ -1,3 +1,4 @@
import { formatErrorMessage } from "../infra/errors.js";
import { estimateBase64DecodedBytes } from "../media/base64.js";
import type { PromptImageOrderEntry } from "../media/prompt-image-order.js";
import { sniffMimeFromBase64 } from "../media/sniff-mime-from-base64.js";
@@ -428,7 +429,7 @@ export async function parseMessageWithAttachments(
isOffloaded = true;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
const errorMessage = formatErrorMessage(err);
throw new MediaOffloadError(
`[Gateway Error] Failed to save intercepted media to disk: ${errorMessage}`,
{ cause: err },

View File

@@ -5,6 +5,7 @@ import {
type DeviceAuthToken,
type PairedDevice,
} from "../infra/device-pairing.js";
import { formatErrorMessage } from "../infra/errors.js";
import type { ExecApprovalRequest, ExecApprovalResolved } from "../infra/exec-approvals.js";
import {
clearApnsRegistrationIfCurrent,
@@ -208,8 +209,7 @@ async function sendRequestedPushes(params: {
);
for (const result of results) {
if (result.status === "rejected") {
const message =
result.reason instanceof Error ? result.reason.message : String(result.reason);
const message = formatErrorMessage(result.reason);
params.log.warn?.(`exec approvals: iOS request push threw error: ${message}`);
}
}
@@ -274,7 +274,7 @@ export function createExecApprovalIosPushDelivery(params: { log: GatewayLikeLogg
nodeIds: plan.targets.map((target) => target.nodeId),
requestPushPromise: sendRequestedPushes({ request, plan, log: params.log }).catch(
(err) => {
const message = err instanceof Error ? err.message : String(err);
const message = formatErrorMessage(err);
params.log.error?.(`exec approvals: iOS request push failed: ${message}`);
return { attempted: plan.targets.length, delivered: 0 };
},

View File

@@ -1,4 +1,5 @@
import crypto from "node:crypto";
import { formatErrorMessage } from "../infra/errors.js";
import {
MCP_LOOPBACK_SERVER_NAME,
MCP_LOOPBACK_SERVER_VERSION,
@@ -75,7 +76,7 @@ export async function handleMcpJsonRpc(params: {
isError: false,
});
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = formatErrorMessage(error);
return jsonRpcResult(id, {
content: [{ type: "text", text: message || "tool execution failed" }],
isError: true,

View File

@@ -1,6 +1,7 @@
import crypto from "node:crypto";
import { createServer as createHttpServer } from "node:http";
import { loadConfig } from "../config/config.js";
import { formatErrorMessage } from "../infra/errors.js";
import { logDebug, logWarn } from "../logger.js";
import { handleMcpJsonRpc } from "./mcp-http.handlers.js";
import {
@@ -72,9 +73,7 @@ export async function startMcpLoopbackServer(port = 0): Promise<{
res.writeHead(200, { "Content-Type": "application/json" });
res.end(payload);
} catch (error) {
logWarn(
`mcp loopback: request handling failed: ${error instanceof Error ? error.message : String(error)}`,
);
logWarn(`mcp loopback: request handling failed: ${formatErrorMessage(error)}`);
if (!res.headersSent) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify(jsonRpcError(null, -32700, "Parse error")));

View File

@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { formatErrorMessage } from "../infra/errors.js";
import type { PromptImageOrderEntry } from "../media/prompt-image-order.js";
import type { NodeEvent, NodeEventContext } from "./server-node-events-types.js";
import {
@@ -408,7 +409,7 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
await deleteMediaBuffer(ref.id);
} catch (cleanupErr) {
ctx.logGateway.warn(
`Failed to cleanup orphaned media ${ref.id}: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`,
`Failed to cleanup orphaned media ${ref.id}: ${formatErrorMessage(cleanupErr)}`,
);
}
}
@@ -416,9 +417,7 @@ export const handleNodeEvent = async (ctx: NodeEventContext, nodeId: string, evt
return;
}
} catch (err) {
ctx.logGateway.warn(
`agent.request attachment parse failed: ${err instanceof Error ? err.message : String(err)}`,
);
ctx.logGateway.warn(`agent.request attachment parse failed: ${formatErrorMessage(err)}`);
return;
}
}

View File

@@ -3,6 +3,7 @@ import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.
import type { CliDeps } from "../cli/deps.js";
import { resolveMainSessionKeyFromConfig } from "../config/sessions.js";
import { parseSessionThreadInfo } from "../config/sessions/thread-info.js";
import { formatErrorMessage } from "../infra/errors.js";
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
import { deliverOutboundPayloads } from "../infra/outbound/deliver.js";
import { ackDelivery, enqueueDelivery, failDelivery } from "../infra/outbound/delivery-queue.js";
@@ -105,11 +106,9 @@ async function deliverRestartSentinelNotice(params: {
});
if (!retrying) {
if (queueId) {
await failDelivery(queueId, err instanceof Error ? err.message : String(err)).catch(
() => {
// Best-effort queue bookkeeping.
},
);
await failDelivery(queueId, formatErrorMessage(err)).catch(() => {
// Best-effort queue bookkeeping.
});
}
return;
}

View File

@@ -1,3 +1,4 @@
import { formatErrorMessage } from "../infra/errors.js";
import {
disableTailscaleFunnel,
disableTailscaleServe,
@@ -33,9 +34,7 @@ export async function startGatewayTailscaleExposure(params: {
params.logTailscale.info(`${params.tailscaleMode} enabled`);
}
} catch (err) {
params.logTailscale.warn(
`${params.tailscaleMode} failed: ${err instanceof Error ? err.message : String(err)}`,
);
params.logTailscale.warn(`${params.tailscaleMode} failed: ${formatErrorMessage(err)}`);
}
if (!params.resetOnExit) {
@@ -51,7 +50,7 @@ export async function startGatewayTailscaleExposure(params: {
}
} catch (err) {
params.logTailscale.warn(
`${params.tailscaleMode} cleanup failed: ${err instanceof Error ? err.message : String(err)}`,
`${params.tailscaleMode} cleanup failed: ${formatErrorMessage(err)}`,
);
}
};

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { hasBinary } from "../agents/skills.js";
import { formatErrorMessage } from "../infra/errors.js";
import { runCommandWithTimeout, type SpawnResult } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import { normalizeServePath } from "./gmail.js";
@@ -52,7 +53,7 @@ function formatCommandResult(command: string, result: SpawnResult): string {
}
function formatJsonParseFailure(command: string, result: SpawnResult, err: unknown): string {
const reason = err instanceof Error ? err.message : String(err);
const reason = formatErrorMessage(err);
return `${command} returned invalid JSON: ${reason}\n${formatCommandResult(command, result)}`;
}

View File

@@ -9,6 +9,7 @@ import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "../config/config.js";
import { openBoundaryFile } from "../infra/boundary-file-read.js";
import { formatErrorMessage } from "../infra/errors.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { sanitizeForLog } from "../terminal/ansi.js";
import { shouldIncludeHook } from "./config.js";
@@ -143,14 +144,12 @@ export async function loadInternalHooks(
loadedCount++;
} catch (err) {
log.error(
`Failed to load hook ${safeLogValue(entry.hook.name)}: ${safeLogValue(err instanceof Error ? err.message : String(err))}`,
`Failed to load hook ${safeLogValue(entry.hook.name)}: ${safeLogValue(formatErrorMessage(err))}`,
);
}
}
} catch (err) {
log.error(
`Failed to load directory-based hooks: ${safeLogValue(err instanceof Error ? err.message : String(err))}`,
);
log.error(`Failed to load directory-based hooks: ${safeLogValue(formatErrorMessage(err))}`);
}
// 2. Load legacy config handlers (backwards compatibility)
@@ -232,7 +231,7 @@ export async function loadInternalHooks(
loadedCount++;
} catch (err) {
log.error(
`Failed to load hook handler from ${safeLogValue(handlerConfig.module)}: ${safeLogValue(err instanceof Error ? err.message : String(err))}`,
`Failed to load hook handler from ${safeLogValue(handlerConfig.module)}: ${safeLogValue(formatErrorMessage(err))}`,
);
}
}

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../config/config.js";
import { formatErrorMessage } from "../infra/errors.js";
import { listBundledPluginMetadata } from "../plugins/bundled-plugin-metadata.js";
import { loadBundledPluginPublicArtifactModuleSync } from "../plugins/public-surface-loader.js";
import type { ResolverContext, SecretDefaults } from "./runtime-shared.js";
@@ -45,7 +46,7 @@ function loadBundledChannelPublicArtifact(
});
} catch (error) {
if (process.env.OPENCLAW_DEBUG_CHANNEL_CONTRACT_API === "1") {
const detail = error instanceof Error ? error.message : String(error);
const detail = formatErrorMessage(error);
process.stderr.write(
`[channel-contract-api] failed to load ${channelId} via ${metadata.dirName}/${artifactBasename}: ${detail}\n`,
);

View File

@@ -2,6 +2,7 @@ import fs from "node:fs";
import path from "node:path";
import { listAgentIds, resolveAgentDir } from "../agents/agent-scope.js";
import type { OpenClawConfig } from "../config/config.js";
import { formatErrorMessage } from "../infra/errors.js";
import { resolveUserPath } from "../utils.js";
import { listAuthProfileStorePaths as listAuthProfileStorePathsFromAuthStorePaths } from "./auth-store-paths.js";
import { parseEnvValue } from "./shared.js";
@@ -126,7 +127,7 @@ export function readJsonObjectIfExists(
} catch (err) {
return {
value: null,
error: err instanceof Error ? err.message : String(err),
error: formatErrorMessage(err),
};
}
}