diff --git a/apps/cloud/src/auth/access-token-options.ts b/apps/cloud/src/auth/access-token-options.ts
index d67915ce70..8bb4c583ec 100644
--- a/apps/cloud/src/auth/access-token-options.ts
+++ b/apps/cloud/src/auth/access-token-options.ts
@@ -1,10 +1,10 @@
import type { JWTVerifyOptions } from "jose";
-/**
- * Require expiring WorkOS tokens. Token lifetime is controlled by WorkOS via
- * `exp`; do not cap the age locally, since AuthKit issues MCP access tokens
- * that live for several days.
- */
+/** WorkOS credentials may authorize a request for at most 24 hours after issuance. */
+export const WORKOS_ACCESS_TOKEN_MAX_AGE_SECONDS = 24 * 60 * 60;
+
+/** Require signed, expiring tokens and cap their effective lifetime even if the issuer sets a later exp. */
export const workosAccessTokenOptions: JWTVerifyOptions = {
requiredClaims: ["exp", "iat"],
+ maxTokenAge: WORKOS_ACCESS_TOKEN_MAX_AGE_SECONDS,
};
diff --git a/apps/cloud/src/auth/errors.ts b/apps/cloud/src/auth/errors.ts
index 6c3c8e3ac2..5720941d11 100644
--- a/apps/cloud/src/auth/errors.ts
+++ b/apps/cloud/src/auth/errors.ts
@@ -228,7 +228,7 @@ export const withServiceLogging = (
effect: Effect.Effect,
): Effect.Effect =>
effect.pipe(
- Effect.tapCause((cause) => Effect.logError(`${name} failed`, cause)),
+ Effect.tapCause(() => Effect.logError(`${name} failed`)),
Effect.mapError(publicError),
Effect.withSpan(name),
) as Effect.Effect;
diff --git a/apps/cloud/src/auth/handlers.ts b/apps/cloud/src/auth/handlers.ts
index c62934948d..5e71add01e 100644
--- a/apps/cloud/src/auth/handlers.ts
+++ b/apps/cloud/src/auth/handlers.ts
@@ -510,7 +510,7 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group(
Effect.gen(function* () {
yield* Effect.logWarning(
"createOrganization: could not provision the Autumn customer",
- { organizationId: org.id, error },
+ { organizationId: org.id },
);
yield* captureCauseEffect(error);
}),
@@ -620,10 +620,10 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group(
{ organizationId },
),
),
- Effect.tapError((error) =>
+ Effect.tapError(() =>
Effect.logError(
"deleteOrganization: org marked deleted but the Autumn customer could not be deleted; retry the deletion",
- { organizationId, error },
+ { organizationId },
),
),
Effect.mapError(() => new OrganizationDeletionIncomplete({ step: "billing" })),
@@ -663,10 +663,10 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group(
s.deleteOrganizationCascade(organizationId, deletedAt),
)
.pipe(
- Effect.tapError((error) =>
+ Effect.tapError(() =>
Effect.logError(
"deleteOrganization: org marked deleted, removed from WorkOS and Autumn, but local purge failed, tenant data and secrets orphaned; retry the deletion",
- { organizationId, error },
+ { organizationId },
),
),
);
diff --git a/apps/cloud/src/auth/workos-events-runner.ts b/apps/cloud/src/auth/workos-events-runner.ts
index e5979e48b4..9990681e97 100644
--- a/apps/cloud/src/auth/workos-events-runner.ts
+++ b/apps/cloud/src/auth/workos-events-runner.ts
@@ -103,7 +103,7 @@ export const runWorkOsEventsSync = (): Promise =>
Effect.scoped,
Effect.catchCause((cause) =>
Effect.gen(function* () {
- yield* Effect.logError("workos_events: sync run failed", cause);
+ yield* Effect.logError("workos_events: sync run failed");
yield* captureCauseEffect(cause);
}),
),
diff --git a/apps/cloud/src/engine/execution-gate.ts b/apps/cloud/src/engine/execution-gate.ts
index 60c102663f..21d6cfd1a3 100644
--- a/apps/cloud/src/engine/execution-gate.ts
+++ b/apps/cloud/src/engine/execution-gate.ts
@@ -175,7 +175,7 @@ export const makeExecutionLimitGate = (checkBalance: ExecutionBalanceCheck) => {
Effect.catch((error: unknown) =>
Effect.gen(function* () {
yield* Effect.sync(() => {
- console.warn("[billing] execution balance check failed open:", error);
+ console.warn("[billing] execution balance check failed open");
});
yield* captureCauseEffect(error);
return { blocked: false } as const satisfies GateDecision;
diff --git a/apps/cloud/src/engine/execution-rate-limit.ts b/apps/cloud/src/engine/execution-rate-limit.ts
index 04c35f5774..3eb432e618 100644
--- a/apps/cloud/src/engine/execution-rate-limit.ts
+++ b/apps/cloud/src/engine/execution-rate-limit.ts
@@ -132,7 +132,7 @@ const failOpen = (
"rate_limit.check.error_tag": outcome.errorTag,
});
yield* Effect.sync(() => {
- console.warn("[rate-limit] execution rate limit check failed open:", error);
+ console.warn("[rate-limit] execution rate limit check failed open");
});
if (!outcome.timedOut) yield* captureCauseEffect(error);
return { blocked: false } as const satisfies GateDecision;
@@ -303,7 +303,6 @@ export const makeExecutionRateLimiter = (
`[rate-limit] exemption lookup failed for ${organizationId}; treating as ${
cached ? "last known" : "not exempt"
}:`,
- error,
);
});
yield* captureCauseEffect(error);
diff --git a/apps/cloud/src/extensions/billing/member-seats.ts b/apps/cloud/src/extensions/billing/member-seats.ts
index 7ed5f2ab19..5ca8c16c23 100644
--- a/apps/cloud/src/extensions/billing/member-seats.ts
+++ b/apps/cloud/src/extensions/billing/member-seats.ts
@@ -64,10 +64,9 @@ export const forkReportMemberSeats = (
waitUntil(Effect.runPromise(autumn.setMemberSeats(organizationId, seats)));
});
}).pipe(
- Effect.catch((error) =>
+ Effect.catch(() =>
Effect.logWarning("reportMemberSeats: seat recount failed", {
organizationId,
- error,
}),
),
Effect.withSpan("billing.reportMemberSeats"),
diff --git a/apps/cloud/src/extensions/billing/route.ts b/apps/cloud/src/extensions/billing/route.ts
index 8269727272..6eca015292 100644
--- a/apps/cloud/src/extensions/billing/route.ts
+++ b/apps/cloud/src/extensions/billing/route.ts
@@ -1,5 +1,5 @@
import { env } from "cloudflare:workers";
-import { Cause, Effect } from "effect";
+import { Effect } from "effect";
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
import { autumnHandler } from "autumn-js/backend";
@@ -138,7 +138,7 @@ const handler = Effect.gen(function* () {
);
if (statusCode >= 400) {
- console.error("[autumn] upstream error:", statusCode, response);
+ console.error("[autumn] upstream error", { status: statusCode });
return yield* new HttpResponseError({
status: statusCode,
code: "billing_request_failed",
@@ -150,7 +150,7 @@ const handler = Effect.gen(function* () {
}).pipe(
Effect.catchCause((err) => {
if (isServerError(err)) {
- console.error("[autumn] request failed:", Cause.pretty(err));
+ console.error("[autumn] request failed", { status: 500 });
}
return toErrorServerResponseEffect(err);
}),
diff --git a/apps/cloud/src/extensions/billing/service.ts b/apps/cloud/src/extensions/billing/service.ts
index af04a5b829..75de6e1fb6 100644
--- a/apps/cloud/src/extensions/billing/service.ts
+++ b/apps/cloud/src/extensions/billing/service.ts
@@ -182,7 +182,7 @@ const make = Effect.sync(() => {
// Silent billing data loss is worth paging on: autumn.trackExecution
// is fire-and-forget so the caller doesn't handle it themselves.
yield* Effect.sync(() => {
- console.error("[billing] track failed:", error);
+ console.error("[billing] track failed");
});
yield* captureCauseEffect(error);
yield* Effect.annotateCurrentSpan({ "autumn.track.failed": true });
@@ -217,7 +217,7 @@ const make = Effect.sync(() => {
// Silent seat drift means wrong invoices, so failures page just
// like a lost execution track.
yield* Effect.sync(() => {
- console.error("[billing] seat sync failed:", error);
+ console.error("[billing] seat sync failed");
});
yield* captureCauseEffect(error);
yield* Effect.annotateCurrentSpan({ "autumn.members.failed": true });
diff --git a/apps/cloud/src/observability/error-logging.ts b/apps/cloud/src/observability/error-logging.ts
index 008bbaf653..2583534472 100644
--- a/apps/cloud/src/observability/error-logging.ts
+++ b/apps/cloud/src/observability/error-logging.ts
@@ -1,17 +1,6 @@
import { Cause, Effect, Option, Predicate, Result } from "effect";
import { HttpRouter, HttpServerRequest } from "effect/unstable/http";
-const MAX_LOGGED_CAUSE_CHARS = 4_000;
-
-const truncate = (value: string): string =>
- value.length <= MAX_LOGGED_CAUSE_CHARS
- ? value
- : `${value.slice(0, MAX_LOGGED_CAUSE_CHARS)}\n...[truncated ${
- value.length - MAX_LOGGED_CAUSE_CHARS
- } chars]`;
-
-const loggedCause = (cause: Cause.Cause): string => truncate(Cause.pretty(cause));
-
const objectValue = (value: unknown, key: string): unknown =>
Predicate.hasProperty(value, key) ? value[key] : undefined;
@@ -65,7 +54,8 @@ export const logApiErrorCause = (
path: requestPath(request),
status: httpStatus(error),
errorTag: errorTag(error),
- cause: loggedCause(cause),
+ // Error causes can contain provider responses, request bodies, and secrets.
+ // Keep only the classification; never serialize the exception or its stack.
});
};
diff --git a/apps/cloud/src/observability/index.ts b/apps/cloud/src/observability/index.ts
index 88b88419e5..f43022c76a 100644
--- a/apps/cloud/src/observability/index.ts
+++ b/apps/cloud/src/observability/index.ts
@@ -7,7 +7,7 @@
// `withObservability` (in @executor-js/api) wraps every handler effect; when
// it sees an unmapped cause it asks `ErrorCapture.captureException` for a
// trace id and fails with `InternalError({ traceId })`. The client gets
-// the opaque id, we get the full cause + stack in Sentry.
+// the opaque id; Sentry receives a minimized diagnostic event.
// ---------------------------------------------------------------------------
import * as Sentry from "@sentry/cloudflare";
@@ -15,16 +15,12 @@ import type { ErrorEvent, Scope } from "@sentry/cloudflare";
import { Cause, Effect, Layer, Predicate } from "effect";
import type * as Tracer from "effect/Tracer";
+import { minimizeSentryEvent } from "./sentry-privacy";
+
import { ErrorCapture } from "@executor-js/api";
import { classifyDurableObjectError } from "@executor-js/cloudflare/mcp/durable-object-errors";
import { withStableGroupingFingerprint } from "@executor-js/sdk/sentry-grouping";
-// Drizzle/postgres-js include the failing SQL (params + bound values) in
-// their error message. For OpenAPI source inserts that's 1MB+ of spec
-// text which blows past terminal scrollback and hides the actual pg
-// error. Sentry still receives the full, untruncated cause via
-// `setExtra`; only the dev-console mirror is capped.
-const MAX_CONSOLE_CAUSE_CHARS = 4_000;
const OTEL_TRACE_ID_PATTERN = /^[0-9a-f]{32}$/;
const OTEL_SPAN_ID_PATTERN = /^[0-9a-f]{16}$/;
@@ -52,11 +48,6 @@ export type OtelCorrelationContext = {
readonly spanId: string;
};
-const truncate = (s: string): string =>
- s.length <= MAX_CONSOLE_CAUSE_CHARS
- ? s
- : `${s.slice(0, MAX_CONSOLE_CAUSE_CHARS)}\n…[truncated ${s.length - MAX_CONSOLE_CAUSE_CHARS} chars]`;
-
const validOtelContext = (context: OtelCorrelationContext): boolean =>
OTEL_TRACE_ID_PATTERN.test(context.traceId) && OTEL_SPAN_ID_PATTERN.test(context.spanId);
@@ -212,7 +203,7 @@ export const beforeSendCloudEvent = (
options?: { readonly logPayload?: boolean },
): ErrorEvent | null => {
const reported = beforeSendWithOtelCorrelation(event, options);
- return reported === null ? null : withStableGroupingFingerprint(reported);
+ return reported === null ? null : minimizeSentryEvent(withStableGroupingFingerprint(reported));
};
/**
@@ -230,8 +221,8 @@ export const beforeSendCloudEvent = (
export const cloudSentryOptions = (env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 0,
- enableLogs: true,
- sendDefaultPii: true,
+ enableLogs: false,
+ sendDefaultPii: false,
skipOpenTelemetrySetup: true,
beforeSend: (event: ErrorEvent) =>
beforeSendCloudEvent(event, {
@@ -365,7 +356,7 @@ export const ErrorCaptureLive: Layer.Layer = Layer.succeed(
ErrorCapture.of({
captureException: (cause) =>
Effect.gen(function* () {
- console.error("[api] unhandled cause:", truncate(Cause.pretty(cause)));
+ console.error("[api] unhandled cause", classificationTagsOf(cause));
return (yield* captureCauseEffect(cause)) ?? "";
}),
}),
diff --git a/apps/cloud/src/observability/redact-span-urls.ts b/apps/cloud/src/observability/redact-span-urls.ts
index ced0513214..c78a966451 100644
--- a/apps/cloud/src/observability/redact-span-urls.ts
+++ b/apps/cloud/src/observability/redact-span-urls.ts
@@ -110,6 +110,10 @@ export class UrlRedactingSpanProcessor implements SpanProcessor {
if (name !== event.name) event.name = name;
if (event.attributes === undefined) continue;
for (const [key, value] of Object.entries(event.attributes)) {
+ if (key === "exception.message" || key === "exception.stacktrace") {
+ event.attributes[key] = "[REDACTED]";
+ continue;
+ }
// Event attributes permit string[] exactly as span attributes do, so
// array elements get the same free-text scrub, in place.
if (Array.isArray(value)) {
@@ -124,8 +128,7 @@ export class UrlRedactingSpanProcessor implements SpanProcessor {
const message = span.status.message;
if (typeof message === "string") {
- const redacted = redactUrlsInText(message);
- if (redacted !== message) span.status.message = redacted;
+ span.status.message = "[REDACTED]";
}
}
}
diff --git a/apps/cloud/src/observability/sentry-privacy.ts b/apps/cloud/src/observability/sentry-privacy.ts
new file mode 100644
index 0000000000..ff9cf7c6d6
--- /dev/null
+++ b/apps/cloud/src/observability/sentry-privacy.ts
@@ -0,0 +1,58 @@
+import type { ErrorEvent } from "@sentry/cloudflare";
+
+const SAFE_TAGS = new Set([
+ "otel_trace_id",
+ "otel_span_id",
+ "operation",
+ "reason",
+ "status",
+ "mcp.do.cause_owner",
+]);
+
+const identifier = (value: string | undefined): string | undefined =>
+ value !== undefined && /^[\w.$<>:/@ -]{1,160}$/.test(value) ? value : undefined;
+
+const sourceFile = (value: string | undefined): string | undefined => {
+ if (!value) return undefined;
+ const path = URL.canParse(value) ? new URL(value).pathname : value.split(/[?#]/)[0];
+ return path && /^\/?(?:assets\/)?[\w./-]+\.(?:js|mjs|ts|tsx)$/.test(path) ? path : undefined;
+};
+
+/** Keep error classification, source positions and correlation; omit all raw payloads. */
+export const minimizeSentryEvent = (event: ErrorEvent): ErrorEvent => ({
+ type: undefined,
+ event_id: event.event_id,
+ timestamp: event.timestamp,
+ platform: event.platform,
+ level: event.level,
+ release: event.release,
+ environment: event.environment,
+ fingerprint: event.fingerprint?.map((part) => identifier(part) ?? "Error"),
+ tags: Object.fromEntries(
+ Object.entries(event.tags ?? {}).filter(
+ ([key, value]) => SAFE_TAGS.has(key) && identifier(String(value)) !== undefined,
+ ),
+ ),
+ exception: {
+ values: event.exception?.values?.map((exception) => ({
+ type: identifier(exception.type) ?? "Error",
+ value: "Details omitted to protect request data",
+ mechanism: exception.mechanism
+ ? {
+ type: identifier(exception.mechanism.type) ?? "generic",
+ handled: exception.mechanism.handled,
+ }
+ : undefined,
+ stacktrace: {
+ frames: exception.stacktrace?.frames?.map((frame) => ({
+ filename: sourceFile(frame.filename),
+ function: identifier(frame.function),
+ module: identifier(frame.module),
+ lineno: frame.lineno,
+ colno: frame.colno,
+ in_app: frame.in_app,
+ })),
+ },
+ })),
+ },
+});
diff --git a/apps/cloud/wrangler.jsonc b/apps/cloud/wrangler.jsonc
index 4ee8b7a2b4..ac07846ebc 100644
--- a/apps/cloud/wrangler.jsonc
+++ b/apps/cloud/wrangler.jsonc
@@ -27,6 +27,7 @@
},
"observability": {
"enabled": true,
+ "redact_query_string": true,
},
"ratelimits": [
{
diff --git a/packages/core/sdk/src/executor.ts b/packages/core/sdk/src/executor.ts
index fdbc9b7671..0f338405cd 100644
--- a/packages/core/sdk/src/executor.ts
+++ b/packages/core/sdk/src/executor.ts
@@ -911,23 +911,6 @@ const storageFailureFromUnknown = (message: string, cause: unknown): StorageFail
const pluginStorageFailure = (pluginId: string, hook: string, cause: unknown): StorageFailure =>
storageFailureFromUnknown(`${hook} failed for plugin ${pluginId}`, cause);
-// oxlint-disable executor/no-instanceof-error, executor/no-unknown-error-message -- boundary: render an arbitrary failure into one readable log field
-/** One-line rendering of a failed rebuild, for the operator-facing warning.
- * A `StorageError` carries the actionable detail in its `cause` (the plugin's
- * own failure) while its own message only names the hook, and structural
- * stringification drops a `cause` that is an `Error` — so unwrap one level and
- * keep both halves. */
-const describeSyncFailure = (error: unknown): string => {
- const base =
- error instanceof Error && error.message.length > 0
- ? error.message
- : Inspectable.toStringUnknown(error, 0);
- const cause = (error as { readonly cause?: unknown } | null | undefined)?.cause;
- if (cause instanceof Error && cause.message.length > 0) return `${base}: ${cause.message}`;
- return base;
-};
-// oxlint-enable executor/no-instanceof-error, executor/no-unknown-error-message
-
const createDefaultMemoryDb = (tables: FumaTables): ExecutorDb => {
const version = "1.0.0";
const latestSchema = fumaSchema>({
@@ -4228,11 +4211,11 @@ export const createExecutor =
Effect.logWarning("executor stale tool sync scan failed", {
- error: describeSyncFailure(error),
+ errorTag: Predicate.isTagged(error, "StorageError") ? "StorageError" : "Unknown",
}),
),
),
diff --git a/packages/core/sdk/src/oauth-service.ts b/packages/core/sdk/src/oauth-service.ts
index de49a7352e..0e71ae02f8 100644
--- a/packages/core/sdk/src/oauth-service.ts
+++ b/packages/core/sdk/src/oauth-service.ts
@@ -14,7 +14,7 @@
// redeems the session, exchanges the code, and mints the connection.
// ---------------------------------------------------------------------------
-import { Duration, Effect, Exit, Layer, Match, Option, Predicate, Schema } from "effect";
+import { Cause, Duration, Effect, Exit, Layer, Match, Option, Predicate, Schema } from "effect";
import { FetchHttpClient, type HttpClient } from "effect/unstable/http";
import { connectionIdentifier } from "./connection-name-identifier";
@@ -1100,7 +1100,7 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
{
owner: input.owner,
client: String(input.slug),
- cause,
+ causeKind: Cause.isCause(cause) ? "Cause" : "Error",
},
).pipe(Effect.as(false)),
),
@@ -2084,7 +2084,9 @@ export const makeOAuthService = (deps: OAuthServiceDeps): OAuthService => {
)
.pipe(
Effect.catch((failure) =>
- Effect.logWarning("executor oauth expired-session sweep failed", { cause: failure }),
+ Effect.logWarning("executor oauth expired-session sweep failed", {
+ failureType: typeof failure,
+ }),
),
);
diff --git a/packages/core/sdk/src/subject-registry.ts b/packages/core/sdk/src/subject-registry.ts
index ca02cef65a..c103c13c64 100644
--- a/packages/core/sdk/src/subject-registry.ts
+++ b/packages/core/sdk/src/subject-registry.ts
@@ -186,7 +186,7 @@ export const touchSubject = (db: FumaDb, input: TouchSubjectInput): Effect.
Effect.logWarning("executor subject touch failed", {
tenant: input.tenant,
externalId: input.externalId,
- cause,
+ failureType: typeof cause,
}),
),
Effect.withSpan("executor.subject.touch"),
diff --git a/packages/hosts/mcp/src/tool-server.ts b/packages/hosts/mcp/src/tool-server.ts
index f7c1a349f7..91562be746 100644
--- a/packages/hosts/mcp/src/tool-server.ts
+++ b/packages/hosts/mcp/src/tool-server.ts
@@ -395,11 +395,6 @@ const readDebugDefault = (): boolean => {
return value === "1" || value === "true";
};
-const capabilitySnapshot = (server: McpServer) => ({
- clientCapabilities: server.server.getClientCapabilities() ?? null,
- elicitationSupport: getElicitationSupport(server),
-});
-
class McpNativeElicitationTransportError extends Data.TaggedError(
"McpNativeElicitationTransportError",
)<{
@@ -835,10 +830,9 @@ const toMcpFailureResult = (cause: Cause.Cause): McpToolResult => {
Predicate.isTagged("McpNativeElicitationTransportError")(defect.success);
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: best-effort defect logging must tolerate non-serializable causes
try {
- console.error(
- `[executor:mcp] execute defect correlation_id=${correlationId}`,
- Cause.pretty(cause),
- );
+ console.error(`[executor:mcp] execute defect correlation_id=${correlationId}`, {
+ nativeElicitationFailed,
+ });
} catch {
/* ignore logger failures */
}
@@ -2383,9 +2377,14 @@ export const createExecutorMcpServer = (
smoke(input.code),
).pipe(
Effect.catchCause((cause) =>
- Effect.as(Effect.logWarning("create-artifact smoke render was unavailable", cause), {
- status: "ok",
- } satisfies ArtifactSmokeRenderResult),
+ Effect.as(
+ Effect.logWarning("create-artifact smoke render was unavailable", {
+ causeKind: Cause.isCause(cause) ? "Cause" : "Error",
+ }),
+ {
+ status: "ok",
+ } satisfies ArtifactSmokeRenderResult,
+ ),
),
);
const renderRejection = smokeRenderRejection(smokeResult);
@@ -2896,7 +2895,7 @@ export const createExecutorMcpServer = (
console.error(
"[executor] MCP session mode",
JSON.stringify({
- ...capabilitySnapshot(server),
+ elicitationSupport: getElicitationSupport(server),
elicitationMode: elicitationMode.mode,
resumeEnabled: elicitationMode.mode !== "native",
}),
diff --git a/packages/plugins/graphql/src/sdk/introspect.ts b/packages/plugins/graphql/src/sdk/introspect.ts
index d484dcdfb7..6949fc621d 100644
--- a/packages/plugins/graphql/src/sdk/introspect.ts
+++ b/packages/plugins/graphql/src/sdk/introspect.ts
@@ -308,7 +308,9 @@ export const introspect = Effect.fn("GraphQL.introspect")(function* (
}
const response = yield* client.execute(request).pipe(
- Effect.tapCause((cause) => Effect.logError("graphql introspection request failed", cause)),
+ Effect.tapCause(() =>
+ Effect.logError("graphql introspection request failed", { host: requestUrl.hostname }),
+ ),
Effect.mapError(
() =>
new GraphqlIntrospectionError({
@@ -339,7 +341,7 @@ export const introspect = Effect.fn("GraphQL.introspect")(function* (
}
const raw = yield* response.json.pipe(
- Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)),
+ Effect.tapCause(() => Effect.logError("graphql introspection JSON parse failed")),
Effect.mapError(
() =>
new GraphqlIntrospectionError({