From bfd7af170a1c0812fd5fb0dbfc784c918f175ece Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 18 Apr 2026 07:25:31 -0500 Subject: [PATCH] fix(server): bound high-volume issue and log reads --- packages/adapter-utils/src/server-utils.ts | 9 ++- server/src/services/heartbeat.ts | 74 ++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index c6fc3ea8ec0..4b3c4a792d4 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -212,7 +212,6 @@ export function appendWithByteCap(prev: string, chunk: string, cap = MAX_CAPTURE while (start < buffer.length && (buffer[start]! & 0xc0) === 0x80) start += 1; return buffer.subarray(start).toString("utf8"); } - function resumeReadable(readable: { resume: () => unknown; destroyed?: boolean } | null | undefined) { if (!readable || readable.destroyed) return; readable.resume(); @@ -1421,6 +1420,10 @@ export async function runChildProcess( maybeArmTerminalResultCleanup(); resumeReadable(readable); }); + .finally(() => { + maybeArmTerminalResultCleanup(); + resumeReadable(readable); + }); }); child.stderr?.on("data", (chunk: unknown) => { @@ -1437,6 +1440,10 @@ export async function runChildProcess( maybeArmTerminalResultCleanup(); resumeReadable(readable); }); + .finally(() => { + maybeArmTerminalResultCleanup(); + resumeReadable(readable); + }); }); const stdin = child.stdin; diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index 6a4dd65e303..32421fc2c28 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -705,6 +705,80 @@ export function boundHeartbeatRunEventPayloadForStorage(payload: Record): unknown { + if (typeof value === "string") { + return truncateRunEventString(value); + } + if ( + value === null + || typeof value === "number" + || typeof value === "boolean" + ) { + return value; + } + if (value instanceof Date) { + return value.toISOString(); + } + if (Array.isArray(value)) { + if (depth >= MAX_RUN_EVENT_PAYLOAD_DEPTH) { + return { + _truncated: true, + type: "array", + originalLength: value.length, + }; + } + const bounded = value + .slice(0, MAX_RUN_EVENT_PAYLOAD_ARRAY_ITEMS) + .map((entry) => boundRunEventValue(entry, depth + 1, seen)); + if (value.length > MAX_RUN_EVENT_PAYLOAD_ARRAY_ITEMS) { + bounded.push({ + _truncated: true, + omittedItems: value.length - MAX_RUN_EVENT_PAYLOAD_ARRAY_ITEMS, + }); + } + return bounded; + } + if (typeof value !== "object" || value === undefined) { + return null; + } + if (seen.has(value)) { + return "[Circular]"; + } + seen.add(value); + const entries = Object.entries(value as Record); + if (depth >= MAX_RUN_EVENT_PAYLOAD_DEPTH) { + const bounded = { + _truncated: true, + type: "object", + keys: entries.map(([key]) => key).slice(0, 20), + }; + seen.delete(value); + return bounded; + } + + const out: Record = {}; + for (const [key, entryValue] of entries.slice(0, MAX_RUN_EVENT_PAYLOAD_OBJECT_KEYS)) { + out[key] = boundRunEventValue(entryValue, depth + 1, seen); + } + if (entries.length > MAX_RUN_EVENT_PAYLOAD_OBJECT_KEYS) { + out._truncated = true; + out._omittedKeys = entries.length - MAX_RUN_EVENT_PAYLOAD_OBJECT_KEYS; + } + seen.delete(value); + return out; +} + +export function boundHeartbeatRunEventPayloadForStorage(payload: Record): Record { + const bounded = boundRunEventValue(payload, 0, new WeakSet()); + return parseObject(bounded) ?? { _truncated: true }; +} + function redactInlineBase64ImageData(chunk: string) { return chunk.replace(INLINE_BASE64_IMAGE_DATA_RE, (_match, prefix: string, data: string, suffix: string) => `${prefix}[omitted base64 image data: ${data.length} chars]${suffix}`,