Sentry Issue
CLI-20C — ZodError: Expected object, received string
Summary
sentry log list crashes with an unhandled ZodError when a self-hosted Sentry instance returns a non-object response (e.g., a plain text error or HTML page) for the /events/?dataset=logs endpoint. This typically happens when the self-hosted instance does not support the logs dataset or a reverse proxy intercepts the request.
Root Cause
The listLogs() function in src/lib/api/logs.ts uses LogsResponseSchema.parse(data) (which throws ZodError on failure) instead of .safeParse() (which returns an error object). This is the only API module in the codebase that uses raw .parse() — all others either use apiRequestToRegion with a schema: option (which does .safeParse() + wraps failures in ApiError) or have manual type checks.
How the API returns a string instead of an object
The @sentry/api SDK's HTTP client has response-parsing logic that depends on the Content-Type header:
Content-Type: text/html → SDK returns data = await response.text() (a raw HTML string)
Content-Type: text/plain → SDK returns data = await response.text() (a raw text string)
Content-Type: application/json with body "error message" → SDK returns the string from JSON.parse
unwrapResult() at infrastructure.ts:254 performs return data as T with no runtime type checking — it only checks if error is defined (which it isn't for 2xx responses).
Crash Site
// src/lib/api/logs.ts:130-132
const data = unwrapResult(result, "Failed to list logs");
const logsResponse = LogsResponseSchema.parse(data); // <-- throws ZodError
return logsResponse.data;
Affected Functions
Both listLogs() (line 131) and getLogsBatch() (line ~194) have the identical vulnerable .parse() pattern.
Existing Pattern in the Codebase
The correct pattern already exists in src/lib/api/infrastructure.ts:477-493:
// apiRequestToRegion with schema option:
const result = schema.safeParse(data);
if (!result.success) {
Sentry.setContext("zod_validation", { endpoint, status, issues });
throw new ApiError("Unexpected response format from ...", status, result.error.message);
}
And in src/lib/api/organizations.ts:66-73:
if (!Array.isArray(data)) {
throw new ApiError(
"Failed to list organizations: unexpected response format",
0,
`Expected an array but received ${typeof data}. ` +
"This may indicate an incompatible self-hosted Sentry version or a proxy interfering."
);
}
Proposed Fix
- In
src/lib/api/logs.ts, replace .parse() with .safeParse() + throw a descriptive ApiError with self-hosted-aware messaging — for both listLogs() and getLogsBatch()
- Add a pre-validation
typeof data check for early, descriptive error messages (matching organizations.ts pattern)
- Add unit tests verifying that string/non-object responses produce a clear
ApiError instead of an unhandled ZodError
Environment
- Release: 0.36.0
- Platform: node (self-hosted Sentry, Ubuntu Linux 24.04)
- Command:
sentry log list
- Self-hosted: Yes (
is_self_hosted: True)
Sentry Issue
CLI-20C —
ZodError: Expected object, received stringSummary
sentry log listcrashes with an unhandledZodErrorwhen a self-hosted Sentry instance returns a non-object response (e.g., a plain text error or HTML page) for the/events/?dataset=logsendpoint. This typically happens when the self-hosted instance does not support thelogsdataset or a reverse proxy intercepts the request.Root Cause
The
listLogs()function insrc/lib/api/logs.tsusesLogsResponseSchema.parse(data)(which throwsZodErroron failure) instead of.safeParse()(which returns an error object). This is the only API module in the codebase that uses raw.parse()— all others either useapiRequestToRegionwith aschema:option (which does.safeParse()+ wraps failures inApiError) or have manual type checks.How the API returns a string instead of an object
The
@sentry/apiSDK's HTTP client has response-parsing logic that depends on theContent-Typeheader:Content-Type: text/html→ SDK returnsdata = await response.text()(a raw HTML string)Content-Type: text/plain→ SDK returnsdata = await response.text()(a raw text string)Content-Type: application/jsonwith body"error message"→ SDK returns the string fromJSON.parseunwrapResult()atinfrastructure.ts:254performsreturn data as Twith no runtime type checking — it only checks iferroris defined (which it isn't for 2xx responses).Crash Site
Affected Functions
Both
listLogs()(line 131) andgetLogsBatch()(line ~194) have the identical vulnerable.parse()pattern.Existing Pattern in the Codebase
The correct pattern already exists in
src/lib/api/infrastructure.ts:477-493:And in
src/lib/api/organizations.ts:66-73:Proposed Fix
src/lib/api/logs.ts, replace.parse()with.safeParse()+ throw a descriptiveApiErrorwith self-hosted-aware messaging — for bothlistLogs()andgetLogsBatch()typeof datacheck for early, descriptive error messages (matchingorganizations.tspattern)ApiErrorinstead of an unhandledZodErrorEnvironment
sentry log listis_self_hosted: True)