fix(run): isolate CLI telemetry failures
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
import { afterEach, describe, expect, it, mock } from "bun:test"
|
||||||
|
|
||||||
|
describe("run telemetry isolation", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("does not crash CLI run when telemetry throws", async () => {
|
||||||
|
// given
|
||||||
|
mock.module("../../plugin-config", () => ({
|
||||||
|
loadPluginConfig: mock(() => ({})),
|
||||||
|
}))
|
||||||
|
mock.module("./agent-resolver", () => ({
|
||||||
|
resolveRunAgent: mock(() => "Sisyphus - Ultraworker"),
|
||||||
|
}))
|
||||||
|
mock.module("./events", () => ({
|
||||||
|
createEventState: mock(() => ({
|
||||||
|
messageCount: 0,
|
||||||
|
lastPartText: "Run completed",
|
||||||
|
agentColorsByName: {},
|
||||||
|
})),
|
||||||
|
processEvents: mock(async () => {}),
|
||||||
|
serializeError: (error: unknown) => (error instanceof Error ? error.message : String(error)),
|
||||||
|
}))
|
||||||
|
mock.module("./server-connection", () => ({
|
||||||
|
createServerConnection: mock(async () => ({
|
||||||
|
client: {
|
||||||
|
event: {
|
||||||
|
subscribe: mock(async () => ({ stream: {} })),
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
promptAsync: mock(async () => undefined),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cleanup: mock(() => {}),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
mock.module("./session-resolver", () => ({
|
||||||
|
resolveSession: mock(async () => "ses_test"),
|
||||||
|
}))
|
||||||
|
mock.module("./json-output", () => ({
|
||||||
|
createJsonOutputManager: mock(() => ({
|
||||||
|
redirectToStderr: mock(() => {}),
|
||||||
|
restore: mock(() => {}),
|
||||||
|
emitResult: mock(() => {}),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
mock.module("./on-complete-hook", () => ({
|
||||||
|
executeOnCompleteHook: mock(async () => {}),
|
||||||
|
}))
|
||||||
|
mock.module("./model-resolver", () => ({
|
||||||
|
resolveRunModel: mock(() => null),
|
||||||
|
}))
|
||||||
|
mock.module("./poll-for-completion", () => ({
|
||||||
|
pollForCompletion: mock(async () => 0),
|
||||||
|
}))
|
||||||
|
mock.module("./agent-profile-colors", () => ({
|
||||||
|
loadAgentProfileColors: mock(async () => ({})),
|
||||||
|
}))
|
||||||
|
mock.module("./stdin-suppression", () => ({
|
||||||
|
suppressRunInput: mock(() => mock(() => {})),
|
||||||
|
}))
|
||||||
|
mock.module("./timestamp-output", () => ({
|
||||||
|
createTimestampedStdoutController: mock(() => ({
|
||||||
|
enable: mock(() => {}),
|
||||||
|
restore: mock(() => {}),
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
mock.module("../../shared/posthog", () => ({
|
||||||
|
createCliPostHog: mock(() => ({
|
||||||
|
trackActive: () => {
|
||||||
|
throw new Error("telemetry failed")
|
||||||
|
},
|
||||||
|
capture: mock(() => {}),
|
||||||
|
captureException: mock(() => {}),
|
||||||
|
shutdown: mock(async () => {
|
||||||
|
throw new Error("shutdown failed")
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
getPostHogDistinctId: mock(() => "run-distinct-id"),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { run } = await import(`./runner?telemetry=${Date.now()}-${Math.random()}`)
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await run({ message: "test" })
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
+71
-43
@@ -53,17 +53,25 @@ export async function run(options: RunOptions): Promise<number> {
|
|||||||
|
|
||||||
const posthog = createCliPostHog()
|
const posthog = createCliPostHog()
|
||||||
const distinctId = getPostHogDistinctId()
|
const distinctId = getPostHogDistinctId()
|
||||||
posthog.trackActive(distinctId, "run_started")
|
try {
|
||||||
posthog.capture({
|
posthog.trackActive(distinctId, "run_started")
|
||||||
distinctId,
|
} catch {
|
||||||
event: "run_started",
|
// telemetry failure is non-fatal, silently ignore
|
||||||
properties: {
|
}
|
||||||
command: "run",
|
try {
|
||||||
agent: resolvedAgent,
|
posthog.capture({
|
||||||
has_model: !!options.model,
|
distinctId,
|
||||||
has_session_id: !!options.sessionId,
|
event: "run_started",
|
||||||
},
|
properties: {
|
||||||
})
|
command: "run",
|
||||||
|
agent: resolvedAgent,
|
||||||
|
has_model: !!options.model,
|
||||||
|
has_session_id: !!options.sessionId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// telemetry failure is non-fatal, silently ignore
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resolvedModel = resolveRunModel(options.model)
|
const resolvedModel = resolveRunModel(options.model)
|
||||||
@@ -157,27 +165,35 @@ export async function run(options: RunOptions): Promise<number> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
posthog.capture({
|
try {
|
||||||
distinctId,
|
posthog.capture({
|
||||||
event: "run_completed",
|
distinctId,
|
||||||
properties: {
|
event: "run_completed",
|
||||||
command: "run",
|
properties: {
|
||||||
agent: resolvedAgent,
|
command: "run",
|
||||||
duration_ms: durationMs,
|
agent: resolvedAgent,
|
||||||
message_count: eventState.messageCount,
|
duration_ms: durationMs,
|
||||||
},
|
message_count: eventState.messageCount,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// telemetry failure is non-fatal, silently ignore
|
||||||
|
}
|
||||||
} else if (exitCode === 1) {
|
} else if (exitCode === 1) {
|
||||||
posthog.capture({
|
try {
|
||||||
distinctId,
|
posthog.capture({
|
||||||
event: "run_failed",
|
distinctId,
|
||||||
properties: {
|
event: "run_failed",
|
||||||
command: "run",
|
properties: {
|
||||||
agent: resolvedAgent,
|
command: "run",
|
||||||
exit_code: exitCode,
|
agent: resolvedAgent,
|
||||||
duration_ms: durationMs,
|
exit_code: exitCode,
|
||||||
},
|
duration_ms: durationMs,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// telemetry failure is non-fatal, silently ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exitCode
|
return exitCode
|
||||||
@@ -194,21 +210,33 @@ export async function run(options: RunOptions): Promise<number> {
|
|||||||
if (err instanceof Error && err.name === "AbortError") {
|
if (err instanceof Error && err.name === "AbortError") {
|
||||||
return 130
|
return 130
|
||||||
}
|
}
|
||||||
posthog.captureException(err, distinctId)
|
try {
|
||||||
posthog.capture({
|
posthog.captureException(err, distinctId)
|
||||||
distinctId,
|
} catch {
|
||||||
event: "run_failed",
|
// telemetry failure is non-fatal, silently ignore
|
||||||
properties: {
|
}
|
||||||
command: "run",
|
try {
|
||||||
agent: resolvedAgent,
|
posthog.capture({
|
||||||
error: serializeError(err),
|
distinctId,
|
||||||
duration_ms: Date.now() - startTime,
|
event: "run_failed",
|
||||||
},
|
properties: {
|
||||||
})
|
command: "run",
|
||||||
|
agent: resolvedAgent,
|
||||||
|
error: serializeError(err),
|
||||||
|
duration_ms: Date.now() - startTime,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// telemetry failure is non-fatal, silently ignore
|
||||||
|
}
|
||||||
console.error(pc.red(`Error: ${serializeError(err)}`))
|
console.error(pc.red(`Error: ${serializeError(err)}`))
|
||||||
return 1
|
return 1
|
||||||
} finally {
|
} finally {
|
||||||
await posthog.shutdown()
|
try {
|
||||||
|
await posthog.shutdown()
|
||||||
|
} catch {
|
||||||
|
// telemetry failure is non-fatal, silently ignore
|
||||||
|
}
|
||||||
timestampOutput?.restore()
|
timestampOutput?.restore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user