Skip to content

Commit b8b3926

Browse files
Fix isReadOnlyToolName web search misclassification, deduplicate auth parsing, and filter process.env for Claude
- isReadOnlyToolName: exclude tool names containing 'web' from the 'search' substring match so WebSearch tools are routed through classifyToolItemType instead of being short-circuited as file_read_approval. - ProviderHealth: extract shared parseAuthStatusWithLabels and runCliCommand helpers to eliminate near-identical parseClaudeAuthStatusFromOutput / parseAuthStatusFromOutput and runClaudeCommand / runCodexCommand functions. - ClaudeCodeAdapter: filter process.env through createClaudeSpawnEnv, stripping T3CODE_ and VITE_ prefixed variables before passing to the Claude SDK query. Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
1 parent 2121682 commit b8b3926

File tree

2 files changed

+64
-114
lines changed

2 files changed

+64
-114
lines changed

apps/server/src/provider/Layers/ClaudeCodeAdapter.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ export interface ClaudeCodeAdapterLiveOptions {
134134
readonly nativeEventLogger?: EventNdjsonLogger;
135135
}
136136

137+
const CLAUDE_ENV_PREFIX_BLOCKLIST = ["T3CODE_", "VITE_"];
138+
139+
function createClaudeSpawnEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
140+
const filtered: NodeJS.ProcessEnv = {};
141+
for (const [key, value] of Object.entries(baseEnv)) {
142+
if (value === undefined) continue;
143+
const upper = key.toUpperCase();
144+
if (CLAUDE_ENV_PREFIX_BLOCKLIST.some((prefix) => upper.startsWith(prefix))) continue;
145+
filtered[key] = value;
146+
}
147+
return filtered;
148+
}
149+
137150
function isUuid(value: string): boolean {
138151
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
139152
}
@@ -256,7 +269,7 @@ function isReadOnlyToolName(toolName: string): boolean {
256269
normalized.includes("view") ||
257270
normalized.includes("grep") ||
258271
normalized.includes("glob") ||
259-
normalized.includes("search")
272+
(normalized.includes("search") && !normalized.includes("web"))
260273
);
261274
}
262275

@@ -1917,7 +1930,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) {
19171930
...(resumeState?.resumeSessionAt ? { resumeSessionAt: resumeState.resumeSessionAt } : {}),
19181931
includePartialMessages: true,
19191932
canUseTool,
1920-
env: process.env,
1933+
env: createClaudeSpawnEnv(process.env),
19211934
...(input.cwd ? { additionalDirectories: [input.cwd] } : {}),
19221935
};
19231936

apps/server/src/provider/Layers/ProviderHealth.ts

Lines changed: 49 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,39 @@ function extractAuthBoolean(value: unknown): boolean | undefined {
8484
return undefined;
8585
}
8686

87-
export function parseAuthStatusFromOutput(result: CommandResult): {
87+
interface ProviderAuthLabels {
88+
readonly unknownCommandMessage: string;
89+
readonly unauthenticatedMessage: string;
90+
readonly jsonParseWarning: string;
91+
readonly verifyFailurePrefix: string;
92+
readonly loginHints: ReadonlyArray<string>;
93+
}
94+
95+
const CODEX_AUTH_LABELS: ProviderAuthLabels = {
96+
unknownCommandMessage:
97+
"Codex CLI authentication status command is unavailable in this Codex version.",
98+
unauthenticatedMessage: "Codex CLI is not authenticated. Run `codex login` and try again.",
99+
jsonParseWarning:
100+
"Could not verify Codex authentication status from JSON output (missing auth marker).",
101+
verifyFailurePrefix: "Could not verify Codex authentication status",
102+
loginHints: ["run `codex login`", "run codex login"],
103+
};
104+
105+
const CLAUDE_AUTH_LABELS: ProviderAuthLabels = {
106+
unknownCommandMessage:
107+
"Claude Code authentication status command is unavailable in this version of Claude Code.",
108+
unauthenticatedMessage:
109+
"Claude Code is not authenticated. Run `claude auth login` and try again.",
110+
jsonParseWarning:
111+
"Could not verify Claude Code authentication status from JSON output (missing auth marker).",
112+
verifyFailurePrefix: "Could not verify Claude Code authentication status",
113+
loginHints: ["run `claude login`", "run claude login"],
114+
};
115+
116+
function parseAuthStatusWithLabels(
117+
result: CommandResult,
118+
labels: ProviderAuthLabels,
119+
): {
88120
readonly status: ServerProviderStatusState;
89121
readonly authStatus: ServerProviderAuthStatus;
90122
readonly message?: string;
@@ -99,21 +131,20 @@ export function parseAuthStatusFromOutput(result: CommandResult): {
99131
return {
100132
status: "warning",
101133
authStatus: "unknown",
102-
message: "Codex CLI authentication status command is unavailable in this Codex version.",
134+
message: labels.unknownCommandMessage,
103135
};
104136
}
105137

106138
if (
107139
lowerOutput.includes("not logged in") ||
108140
lowerOutput.includes("login required") ||
109141
lowerOutput.includes("authentication required") ||
110-
lowerOutput.includes("run `codex login`") ||
111-
lowerOutput.includes("run codex login")
142+
labels.loginHints.some((hint) => lowerOutput.includes(hint))
112143
) {
113144
return {
114145
status: "error",
115146
authStatus: "unauthenticated",
116-
message: "Codex CLI is not authenticated. Run `codex login` and try again.",
147+
message: labels.unauthenticatedMessage,
117148
};
118149
}
119150

@@ -139,15 +170,14 @@ export function parseAuthStatusFromOutput(result: CommandResult): {
139170
return {
140171
status: "error",
141172
authStatus: "unauthenticated",
142-
message: "Codex CLI is not authenticated. Run `codex login` and try again.",
173+
message: labels.unauthenticatedMessage,
143174
};
144175
}
145176
if (parsedAuth.attemptedJsonParse) {
146177
return {
147178
status: "warning",
148179
authStatus: "unknown",
149-
message:
150-
"Could not verify Codex authentication status from JSON output (missing auth marker).",
180+
message: labels.jsonParseWarning,
151181
};
152182
}
153183
if (result.code === 0) {
@@ -158,12 +188,14 @@ export function parseAuthStatusFromOutput(result: CommandResult): {
158188
return {
159189
status: "warning",
160190
authStatus: "unknown",
161-
message: detail
162-
? `Could not verify Codex authentication status. ${detail}`
163-
: "Could not verify Codex authentication status.",
191+
message: detail ? `${labels.verifyFailurePrefix}. ${detail}` : `${labels.verifyFailurePrefix}.`,
164192
};
165193
}
166194

195+
export function parseAuthStatusFromOutput(result: CommandResult) {
196+
return parseAuthStatusWithLabels(result, CODEX_AUTH_LABELS);
197+
}
198+
167199
// ── Codex CLI config detection ──────────────────────────────────────
168200

169201
/**
@@ -239,10 +271,10 @@ const collectStreamAsString = <E>(stream: Stream.Stream<Uint8Array, E>): Effect.
239271
(acc, chunk) => acc + new TextDecoder().decode(chunk),
240272
);
241273

242-
const runCodexCommand = (args: ReadonlyArray<string>) =>
274+
const runCliCommand = (binary: string, args: ReadonlyArray<string>) =>
243275
Effect.gen(function* () {
244276
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
245-
const command = ChildProcess.make("codex", [...args], {
277+
const command = ChildProcess.make(binary, [...args], {
246278
shell: process.platform === "win32",
247279
});
248280

@@ -260,26 +292,9 @@ const runCodexCommand = (args: ReadonlyArray<string>) =>
260292
return { stdout, stderr, code: exitCode } satisfies CommandResult;
261293
}).pipe(Effect.scoped);
262294

263-
const runClaudeCommand = (args: ReadonlyArray<string>) =>
264-
Effect.gen(function* () {
265-
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
266-
const command = ChildProcess.make("claude", [...args], {
267-
shell: process.platform === "win32",
268-
});
295+
const runCodexCommand = (args: ReadonlyArray<string>) => runCliCommand("codex", args);
269296

270-
const child = yield* spawner.spawn(command);
271-
272-
const [stdout, stderr, exitCode] = yield* Effect.all(
273-
[
274-
collectStreamAsString(child.stdout),
275-
collectStreamAsString(child.stderr),
276-
child.exitCode.pipe(Effect.map(Number)),
277-
],
278-
{ concurrency: "unbounded" },
279-
);
280-
281-
return { stdout, stderr, code: exitCode } satisfies CommandResult;
282-
}).pipe(Effect.scoped);
297+
const runClaudeCommand = (args: ReadonlyArray<string>) => runCliCommand("claude", args);
283298

284299
// ── Health check ────────────────────────────────────────────────────
285300

@@ -409,86 +424,8 @@ export const checkCodexProviderStatus: Effect.Effect<
409424

410425
// ── Claude Code health check ────────────────────────────────────────
411426

412-
export function parseClaudeAuthStatusFromOutput(result: CommandResult): {
413-
readonly status: ServerProviderStatusState;
414-
readonly authStatus: ServerProviderAuthStatus;
415-
readonly message?: string;
416-
} {
417-
const lowerOutput = `${result.stdout}\n${result.stderr}`.toLowerCase();
418-
419-
if (
420-
lowerOutput.includes("unknown command") ||
421-
lowerOutput.includes("unrecognized command") ||
422-
lowerOutput.includes("unexpected argument")
423-
) {
424-
return {
425-
status: "warning",
426-
authStatus: "unknown",
427-
message:
428-
"Claude Code authentication status command is unavailable in this version of Claude Code.",
429-
};
430-
}
431-
432-
if (
433-
lowerOutput.includes("not logged in") ||
434-
lowerOutput.includes("login required") ||
435-
lowerOutput.includes("authentication required") ||
436-
lowerOutput.includes("run `claude login`") ||
437-
lowerOutput.includes("run claude login")
438-
) {
439-
return {
440-
status: "error",
441-
authStatus: "unauthenticated",
442-
message: "Claude Code is not authenticated. Run `claude auth login` and try again.",
443-
};
444-
}
445-
446-
// `claude auth status` returns JSON with a `loggedIn` boolean.
447-
const parsedAuth = (() => {
448-
const trimmed = result.stdout.trim();
449-
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
450-
return { attemptedJsonParse: false as const, auth: undefined as boolean | undefined };
451-
}
452-
try {
453-
return {
454-
attemptedJsonParse: true as const,
455-
auth: extractAuthBoolean(JSON.parse(trimmed)),
456-
};
457-
} catch {
458-
return { attemptedJsonParse: false as const, auth: undefined as boolean | undefined };
459-
}
460-
})();
461-
462-
if (parsedAuth.auth === true) {
463-
return { status: "ready", authStatus: "authenticated" };
464-
}
465-
if (parsedAuth.auth === false) {
466-
return {
467-
status: "error",
468-
authStatus: "unauthenticated",
469-
message: "Claude Code is not authenticated. Run `claude auth login` and try again.",
470-
};
471-
}
472-
if (parsedAuth.attemptedJsonParse) {
473-
return {
474-
status: "warning",
475-
authStatus: "unknown",
476-
message:
477-
"Could not verify Claude Code authentication status from JSON output (missing auth marker).",
478-
};
479-
}
480-
if (result.code === 0) {
481-
return { status: "ready", authStatus: "authenticated" };
482-
}
483-
484-
const detail = detailFromResult(result);
485-
return {
486-
status: "warning",
487-
authStatus: "unknown",
488-
message: detail
489-
? `Could not verify Claude Code authentication status. ${detail}`
490-
: "Could not verify Claude Code authentication status.",
491-
};
427+
export function parseClaudeAuthStatusFromOutput(result: CommandResult) {
428+
return parseAuthStatusWithLabels(result, CLAUDE_AUTH_LABELS);
492429
}
493430

494431
export const checkClaudeCodeProviderStatus: Effect.Effect<

0 commit comments

Comments
 (0)