mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(telemetry): sharpen Sentry signal for v2.1.0 residual defects (#498)
Follow-ups to the v2.1.0 Sentry telemetry overhaul, found by reviewing live release:2.1.0 events: - error_code tag was empty because reportError read only the top-level err.code; add extractErrorCode() to walk the cause chain (pg SQLSTATE, node E-code, else first short code). - InputValidationError from a tool's processV2 in the worker was logged as error_class=bug; classify it as expected for any source. Worker-side ZodError stays a bug (schema drift). - AI dispatcher timeouts rejected with a bare Error, which the sanitizer scrubbed to a message-less "Error: Error"; reject with an operational SafeError (code "timeout") at both timeout sites. Each fix written failing-test-first; affected and adjacent unit suites green plus full CI (integration + e2e).
This commit is contained in:
@@ -271,6 +271,30 @@ describe("bridge - runPythonWithProgress (per-request fallback)", () => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("rejects a per-request timeout as an operational SafeError with a 'timeout' code", async () => {
|
||||
// A timeout is environmental (huge input / slow box), not our bug. Sentry
|
||||
// must see it as an operational SafeError, not a message-less "Error: Error".
|
||||
vi.useFakeTimers();
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const promise = runPythonWithProgress("slow.py", [], { timeout: 1000 });
|
||||
const settled = promise.catch((e: unknown) => e); // attach before firing the timer
|
||||
vi.advanceTimersByTime(1500);
|
||||
mock.emitEvent("close", null, "SIGTERM");
|
||||
|
||||
const err = (await settled) as Error & {
|
||||
isSafeMessage?: unknown;
|
||||
kind?: string;
|
||||
code?: string;
|
||||
};
|
||||
expect(err.message).toBe("Python script timed out");
|
||||
expect(err.isSafeMessage).toBe(true);
|
||||
expect(err.kind).toBe("operational");
|
||||
expect(err.code).toBe("timeout");
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("invokes onProgress callback for JSON progress lines on stderr", async () => {
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
@@ -1390,6 +1414,36 @@ describe("bridge - dispatcher stdin JSON-RPC protocol", () => {
|
||||
expect(result.stdout).toBe('{"success": true}');
|
||||
});
|
||||
|
||||
it("rejects a dispatcher-path timeout as an operational SafeError (NODE-26 regression)", async () => {
|
||||
// NODE-26: the dispatcher request() timeout rejected with a bare
|
||||
// new Error("Python script timed out"), which the Sentry sanitizer scrubbed
|
||||
// to a message-less "Error: Error" bug. It must be an operational SafeError.
|
||||
vi.useFakeTimers();
|
||||
const mock = createMockProcess();
|
||||
vi.mocked(spawn).mockReturnValue(mock.process);
|
||||
|
||||
const initPromise = initDispatcher();
|
||||
mock.stderr.emit("data", Buffer.from('{"ready": true, "gpu": false}\n'));
|
||||
await vi.advanceTimersByTimeAsync(60); // init() polls childReady every 50ms
|
||||
await initPromise;
|
||||
|
||||
const promise = runPythonWithProgress("slow.py", [], { timeout: 1000 });
|
||||
const settled = promise.catch((e: unknown) => e); // attach before firing the timer
|
||||
await vi.advanceTimersByTimeAsync(0); // flush the request's stdin write
|
||||
await vi.advanceTimersByTimeAsync(1100); // fire the dispatcher request timeout
|
||||
|
||||
const err = (await settled) as Error & {
|
||||
isSafeMessage?: unknown;
|
||||
kind?: string;
|
||||
code?: string;
|
||||
};
|
||||
expect(err.message).toBe("Python script timed out");
|
||||
expect(err.isSafeMessage).toBe(true);
|
||||
expect(err.kind).toBe("operational");
|
||||
expect(err.code).toBe("timeout");
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("strips .py extension from script name in dispatcher request", async () => {
|
||||
const mock = await setupReadyDispatcher();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user