Skip to content

@posthog/ai: $ai_error loses error fields (message, stack) and cause chain because json.stringify is used directly on error instances #3556

@shashwat-runable

Description

@shashwat-runable

Bug description

When an error is thrown from inside a model wrapped by withTracing (e.g. a GatewayResponseError from @ai-sdk/gateway carrying a cause chain), @posthog/ai serializes it via json.stringify() before storing it on the $ai_generation event's $ai_error property. Javascript error objects store properties like message, stack, and cause as non enumerable, so when serialized, usually only name remains.

The end result on the posthog dashboard is a stripped down error blob with no message, no stack, and no usable cause chain making it nearly impossible to debug what actually went wrong.

To reproduce

Tested against @posthog/ai@7.11.2 and @posthog/ai@7.18.1 same behaviour.

import { createGateway, generateText } from "ai";
import { withTracing } from "@posthog/ai";
import { PostHog } from "posthog-node";

const phClient = new PostHog(process.env.POSTHOG_KEY!, { host: process.env.POSTHOG_HOST });
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY });

const tracedModel = withTracing(gateway("google/gemini-3-pro-image"), phClient, {
  posthogDistinctId: "test-repro",
  posthogTraceId: `test-repro-${Date.now()}`,
});

// Abort mid-stream to trigger a chained error from the gateway
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);

try {
  await generateText({
    model: tracedModel,
    abortSignal: controller.signal,
    messages: [
      { role: "user", content: "Generate a detailed photo of a sunset over snowy mountains." },
    ],
  });
} catch (err) {
  console.log("PostHog stores:", JSON.stringify(err));
  console.log("Actual error:", {
    name: (err as Error).name,
    message: (err as Error).message,
    cause: (err as { cause?: unknown }).cause,
  });
}

await phClient.shutdown();

Output observed

{
"statusCode":500,
"cause":{},
"name":"GatewayResponseError",
"type":"response_error",
"response":{},
"validationError":{
"name":"AI_TypeValidationError",
"cause":{
"name":"ZodError",
"message":"[\n {\n "expected": "object",\n "code": "invalid_type",\n "path": [\n "error"\n ],\n "message": "Invalid input: expected object, received undefined"\n }\n]"
},
"value":{}
}

message and the cause chain's deeper fields are missing from $ai_error, even though they exist on the actual thrown error.

Related sub-libraries

  • @posthog/ai

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions