authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 11:19:57-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 11:19:57-07:00
loged6ba7439d55b8c2cf7324d5107c2c1cb3ee0920
tree76841c02fc2adfdc500bb72ca99e271d34fa31c4
parentf98448e8721a491f5945f1857cbd22fa87a955ef
signature Signed by PGP key B5690EEEBB952194

[codex] sanitize provider runtime failure diagnostics (#3414)

Co-authored-by: codex <codex@users.noreply.github.com>

8 files changed, 85 insertions(+), 32 deletions(-)

apps/server/src/provider/Layers/ClaudeProvider.ts+10-6
......@@ -31,7 +31,6 @@ import {
3131 buildSelectOptionDescriptor,
3232 buildServerProvider,
3333 DEFAULT_TIMEOUT_MS,
34 detailFromResult,
3534 isCommandMissingCause,
3635 parseGenericCliVersion,
3736 providerModelsFromSettings,
......@@ -661,6 +660,9 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
661660
662661 if (Result.isFailure(versionProbe)) {
663662 const error = versionProbe.failure;
663 yield* Effect.logWarning("Claude Agent CLI health check failed.", {
664 errorTag: error._tag,
665 });
664666 return buildServerProvider({
665667 presentation: CLAUDE_PRESENTATION,
666668 enabled: claudeSettings.enabled,
......@@ -673,7 +675,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
673675 auth: { status: "unknown" },
674676 message: isCommandMissingCause(error)
675677 ? "Claude Agent CLI (`claude`) is not installed or not on PATH."
676 : `Failed to execute Claude Agent CLI health check: ${error instanceof Error ? error.message : String(error)}.`,
678 : "Failed to execute Claude Agent CLI health check.",
677679 },
678680 });
679681 }
......@@ -698,7 +700,11 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
698700 const version = versionProbe.success.value;
699701 const parsedVersion = parseGenericCliVersion(`${version.stdout}\n${version.stderr}`);
700702 if (version.code !== 0) {
701 const detail = detailFromResult(version);
703 yield* Effect.logWarning("Claude Agent CLI version probe exited with a non-zero status.", {
704 exitCode: version.code,
705 stdoutLength: version.stdout.length,
706 stderrLength: version.stderr.length,
707 });
702708 return buildServerProvider({
703709 presentation: CLAUDE_PRESENTATION,
704710 enabled: claudeSettings.enabled,
......@@ -709,9 +715,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(
709715 version: parsedVersion,
710716 status: "error",
711717 auth: { status: "unknown" },
712 message: detail
713 ? `Claude Agent CLI is installed but failed to run. ${detail}`
714 : "Claude Agent CLI is installed but failed to run.",
718 message: "Claude Agent CLI is installed but failed to run.",
715719 },
716720 });
717721 }
apps/server/src/provider/Layers/CursorProvider.ts+7-4
......@@ -10,7 +10,7 @@ import type {
1010} from "@t3tools/contracts";
1111import { ProviderDriverKind } from "@t3tools/contracts";
1212import type * as EffectAcpSchema from "effect-acp/schema";
13import * as Cause from "effect/Cause";
13import { causeErrorTag } from "@t3tools/shared/observability";
1414import * as DateTime from "effect/DateTime";
1515import * as Effect from "effect/Effect";
1616import * as Exit from "effect/Exit";
......@@ -1006,6 +1006,9 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
10061006
10071007 if (Result.isFailure(aboutProbe)) {
10081008 const error = aboutProbe.failure;
1009 yield* Effect.logWarning("Cursor Agent CLI health check failed.", {
1010 errorTag: error._tag,
1011 });
10091012 return buildServerProvider({
10101013 presentation: CURSOR_PRESENTATION,
10111014 enabled: cursorSettings.enabled,
......@@ -1018,7 +1021,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
10181021 auth: { status: "unknown" },
10191022 message: isCommandMissingCause(error)
10201023 ? "Cursor Agent CLI (`agent`) is not installed or not on PATH."
1021 : `Failed to execute Cursor Agent CLI health check: ${error instanceof Error ? error.message : String(error)}.`,
1024 : "Failed to execute Cursor Agent CLI health check.",
10221025 },
10231026 });
10241027 }
......@@ -1074,7 +1077,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(
10741077 );
10751078 if (Exit.isFailure(discoveryExit)) {
10761079 yield* Effect.logWarning("Cursor ACP model discovery failed", {
1077 cause: Cause.pretty(discoveryExit.cause),
1080 errorTag: causeErrorTag(discoveryExit.cause),
10781081 });
10791082 discoveryWarning = "Cursor ACP model discovery failed. Check server logs for details.";
10801083 } else if (Option.isNone(discoveryExit.value)) {
......@@ -1130,7 +1133,7 @@ export const enrichCursorSnapshot = (input: {
11301133 ),
11311134 Effect.catchCause((cause) =>
11321135 Effect.logWarning("Cursor version advisory enrichment failed", {
1133 cause: Cause.pretty(cause),
1136 errorTag: causeErrorTag(cause),
11341137 }).pipe(Effect.asVoid),
11351138 ),
11361139 );
apps/server/src/provider/Layers/GrokProvider.test.ts+4-2
......@@ -54,6 +54,7 @@ it.layer(NodeServices.layer)("checkGrokProviderStatus", (it) => {
5454
5555 it.effect("reports an installed CLI as unhealthy when --version exits non-zero", () =>
5656 Effect.gen(function* () {
57 const secretStderr = "broken grok install: secret-token-value";
5758 const snapshot = yield* Effect.scoped(
5859 Effect.gen(function* () {
5960 const fs = yield* FileSystem.FileSystem;
......@@ -62,7 +63,7 @@ it.layer(NodeServices.layer)("checkGrokProviderStatus", (it) => {
6263 const grokPath = path.join(dir, "grok");
6364 yield* fs.writeFileString(
6465 grokPath,
65 ["#!/bin/sh", 'printf "%s\\n" "broken grok install" >&2', "exit 2", ""].join("\n"),
66 ["#!/bin/sh", `printf "%s\\n" "${secretStderr}" >&2`, "exit 2", ""].join("\n"),
6667 );
6768 yield* fs.chmod(grokPath, 0o755);
6869
......@@ -75,7 +76,8 @@ it.layer(NodeServices.layer)("checkGrokProviderStatus", (it) => {
7576 expect(snapshot.enabled).toBe(true);
7677 expect(snapshot.installed).toBe(true);
7778 expect(snapshot.status).toBe("error");
78 expect(snapshot.message).toContain("broken grok install");
79 expect(snapshot.message).toBe("Grok CLI is installed but failed to run.");
80 expect(snapshot.message).not.toContain(secretStderr);
7981 }),
8082 );
8183
apps/server/src/provider/Layers/GrokProvider.ts+16-11
......@@ -6,7 +6,7 @@ import {
66 type ServerProviderModel,
77} from "@t3tools/contracts";
88import type * as EffectAcpSchema from "effect-acp/schema";
9import * as Cause from "effect/Cause";
9import { causeErrorTag } from "@t3tools/shared/observability";
1010import * as DateTime from "effect/DateTime";
1111import * as Effect from "effect/Effect";
1212import * as Exit from "effect/Exit";
......@@ -19,7 +19,6 @@ import { resolveSpawnCommand } from "@t3tools/shared/shell";
1919
2020import {
2121 buildServerProvider,
22 detailFromResult,
2322 isCommandMissingCause,
2423 parseGenericCliVersion,
2524 providerModelsFromSettings,
......@@ -195,6 +194,9 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
195194
196195 if (Result.isFailure(versionResult)) {
197196 const error = versionResult.failure;
197 yield* Effect.logWarning("Grok CLI health check failed.", {
198 errorTag: error._tag,
199 });
198200 return buildServerProvider({
199201 presentation: GROK_PRESENTATION,
200202 enabled: grokSettings.enabled,
......@@ -207,7 +209,7 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
207209 auth: { status: "unknown" },
208210 message: isCommandMissingCause(error)
209211 ? "Grok CLI (`grok`) is not installed or not on PATH."
210 : `Failed to execute Grok CLI health check: ${error instanceof Error ? error.message : String(error)}.`,
212 : "Failed to execute Grok CLI health check.",
211213 },
212214 });
213215 }
......@@ -231,7 +233,11 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
231233 const versionOutput = versionResult.success.value;
232234 const version = parseGenericCliVersion(`${versionOutput.stdout}\n${versionOutput.stderr}`);
233235 if (versionOutput.code !== 0) {
234 const detail = detailFromResult(versionOutput);
236 yield* Effect.logWarning("Grok CLI version probe exited with a non-zero status.", {
237 exitCode: versionOutput.code,
238 stdoutLength: versionOutput.stdout.length,
239 stderrLength: versionOutput.stderr.length,
240 });
235241 return buildServerProvider({
236242 presentation: GROK_PRESENTATION,
237243 enabled: grokSettings.enabled,
......@@ -242,9 +248,7 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
242248 version,
243249 status: "error",
244250 auth: { status: "unknown" },
245 message: detail
246 ? `Grok CLI is installed but failed to run. ${detail}`
247 : "Grok CLI is installed but failed to run.",
251 message: "Grok CLI is installed but failed to run.",
248252 },
249253 });
250254 }
......@@ -254,8 +258,9 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
254258 Effect.exit,
255259 );
256260 if (Exit.isFailure(discoveryExit)) {
257 const detail = Cause.pretty(discoveryExit.cause);
258 yield* Effect.logWarning("Grok ACP model discovery failed", { cause: detail });
261 yield* Effect.logWarning("Grok ACP model discovery failed", {
262 errorTag: causeErrorTag(discoveryExit.cause),
263 });
259264 return buildServerProvider({
260265 presentation: GROK_PRESENTATION,
261266 enabled: grokSettings.enabled,
......@@ -266,7 +271,7 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func
266271 version,
267272 status: "error",
268273 auth: { status: "unknown" },
269 message: `Grok CLI is installed but ACP startup failed. ${detail}`,
274 message: "Grok CLI is installed but ACP startup failed. Check server logs for details.",
270275 },
271276 });
272277 }
......@@ -324,7 +329,7 @@ export const enrichGrokSnapshot = (input: {
324329 Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
325330 Effect.catchCause((cause) =>
326331 Effect.logWarning("Grok version advisory enrichment failed", {
327 cause: Cause.pretty(cause),
332 errorTag: causeErrorTag(cause),
328333 }),
329334 ),
330335 Effect.asVoid,
apps/server/src/provider/Layers/ProviderRegistry.test.ts+8-5
......@@ -1875,14 +1875,17 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
18751875 }).pipe(Effect.provide(failingSpawnerLayer("spawn claude ENOENT"))),
18761876 );
18771877
1878 it.effect("returns error when version check fails with non-zero exit code", () =>
1879 Effect.gen(function* () {
1878 it.effect("returns error when version check fails with non-zero exit code", () => {
1879 const secretStderr = "Something went wrong: secret-token-value";
1880 return Effect.gen(function* () {
18801881 const status = yield* checkClaudeProviderStatus(
18811882 defaultClaudeSettings,
18821883 claudeCapabilities(),
18831884 );
18841885 assert.strictEqual(status.status, "error");
18851886 assert.strictEqual(status.installed, true);
1887 assert.strictEqual(status.message, "Claude Agent CLI is installed but failed to run.");
1888 assert.ok(!(status.message ?? "").includes(secretStderr));
18861889 }).pipe(
18871890 Effect.provide(
18881891 mockSpawnerLayer((args) => {
......@@ -1890,14 +1893,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), Te
18901893 if (joined === "--version")
18911894 return {
18921895 stdout: "",
1893 stderr: "Something went wrong",
1896 stderr: secretStderr,
18941897 code: 1,
18951898 };
18961899 throw new Error(`Unexpected args: ${joined}`);
18971900 }),
18981901 ),
1899 ),
1900 );
1902 );
1903 });
19011904
19021905 it.effect("returns warning when the Claude initialization result is unavailable", () =>
19031906 Effect.gen(function* () {
apps/server/src/provider/Layers/ProviderService.ts+4-2
......@@ -24,7 +24,7 @@ import {
2424 type ProviderRuntimeEvent,
2525 type ProviderSession,
2626} from "@t3tools/contracts";
27import * as Cause from "effect/Cause";
27import { causeErrorTag } from "@t3tools/shared/observability";
2828import * as DateTime from "effect/DateTime";
2929import * as Effect from "effect/Effect";
3030import * as Layer from "effect/Layer";
......@@ -1061,7 +1061,9 @@ const makeProviderService = Effect.fn("makeProviderService")(function* (
10611061 yield* Effect.addFinalizer(() =>
10621062 runStopAll().pipe(
10631063 Effect.catchCause((cause) =>
1064 Effect.logWarning("failed to stop provider service", { cause: Cause.pretty(cause) }),
1064 Effect.logWarning("failed to stop provider service", {
1065 errorTag: causeErrorTag(cause),
1066 }),
10651067 ),
10661068 ),
10671069 );
apps/server/src/provider/providerStatusCache.test.ts+34
......@@ -9,6 +9,7 @@ import { createModelCapabilities } from "@t3tools/shared/model";
99import { assert, it } from "@effect/vitest";
1010import * as Effect from "effect/Effect";
1111import * as FileSystem from "effect/FileSystem";
12import * as Logger from "effect/Logger";
1213
1314import {
1415 hydrateCachedProvider,
......@@ -42,6 +43,39 @@ const makeProvider = (
4243});
4344
4445it.layer(NodeServices.layer)("providerStatusCache", (it) => {
46 it.effect("logs structural diagnostics without retaining invalid cache contents", () => {
47 const messages: Array<unknown> = [];
48 const logger = Logger.make<unknown, void>((options) => {
49 if (Array.isArray(options.message)) {
50 messages.push(...options.message);
51 } else {
52 messages.push(options.message);
53 }
54 });
55
56 return Effect.gen(function* () {
57 const fs = yield* FileSystem.FileSystem;
58 const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-provider-cache-invalid-" });
59 const cachePath = `${tempDir}/provider.json`;
60 const secretCacheValue = "secret-cache-value";
61 yield* fs.writeFileString(cachePath, `{ "token": "${secretCacheValue}" }`);
62
63 const result = yield* readProviderStatusCache(cachePath);
64
65 assert.strictEqual(result, undefined);
66 const failure = messages.find(
67 (message): message is Record<string, unknown> =>
68 typeof message === "object" && message !== null && "path" in message,
69 );
70 assert.exists(failure);
71 assert.strictEqual(failure.path, cachePath);
72 assert.strictEqual(typeof failure.errorTag, "string");
73 assert.ok(!("cause" in failure));
74 assert.ok(!("issues" in failure));
75 assert.ok(!Object.values(failure).map(String).join("\n").includes(secretCacheValue));
76 }).pipe(Effect.provide(Logger.layer([logger], { mergeWithExisting: false })));
77 });
78
4579 it.effect("writes and reads provider status snapshots", () =>
4680 Effect.gen(function* () {
4781 const fs = yield* FileSystem.FileSystem;
apps/server/src/provider/providerStatusCache.ts+2-2
......@@ -4,7 +4,7 @@ import {
44 type ServerProvider,
55 ServerProvider as ServerProviderSchema,
66} from "@t3tools/contracts";
7import * as Cause from "effect/Cause";
7import { causeErrorTag } from "@t3tools/shared/observability";
88import * as Effect from "effect/Effect";
99import * as FileSystem from "effect/FileSystem";
1010import * as Path from "effect/Path";
......@@ -134,7 +134,7 @@ export const readProviderStatusCache = (filePath: string) =>
134134 onFailure: (cause) =>
135135 Effect.logWarning("failed to parse provider status cache, ignoring", {
136136 path: filePath,
137 issues: Cause.pretty(cause),
137 errorTag: causeErrorTag(cause),
138138 }).pipe(Effect.as(undefined)),
139139 onSuccess: Effect.succeed,
140140 }),