fix(codex): handle lsp diagnostics failures
This commit is contained in:
@@ -89,13 +89,29 @@ async function collectDiagnostics(
|
||||
nextIndex += 1;
|
||||
const filePath = filePaths[index];
|
||||
if (filePath === undefined) return;
|
||||
results[index] = { filePath, diagnostics: (await runDiagnostics(filePath)).trim() };
|
||||
results[index] = { filePath, diagnostics: await collectFileDiagnostics(filePath, runDiagnostics) };
|
||||
}
|
||||
}
|
||||
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
||||
return results;
|
||||
}
|
||||
|
||||
async function collectFileDiagnostics(filePath: string, runDiagnostics: DiagnosticsRunner): Promise<string> {
|
||||
try {
|
||||
return (await runDiagnostics(filePath)).trim();
|
||||
} catch (error) {
|
||||
return formatDiagnosticsError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDiagnosticsError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.trim();
|
||||
if (message.length > 0) return message;
|
||||
}
|
||||
return String(error).trim();
|
||||
}
|
||||
|
||||
function formatDiagnosticBlock({ filePath, diagnostics }: DiagnosticBlock): string {
|
||||
return `LSP diagnostics after editing ${filePath}:\n\n${formatDiagnosticsForDisplay(diagnostics)}`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { runLspPostToolUseHook } from "../src/codex-hook.js";
|
||||
|
||||
describe("codex PostToolUse diagnostics errors", () => {
|
||||
it("#given diagnostics runner throws for a mutated file #when the hook evaluates diagnostics #then it returns blocked output with the thrown message", async () => {
|
||||
// given
|
||||
const output = await runLspPostToolUseHook(
|
||||
{
|
||||
tool_name: "write",
|
||||
tool_input: { path: "src/missing.ts" },
|
||||
tool_response: { ok: true },
|
||||
},
|
||||
async (filePath) => {
|
||||
expect(filePath).toBe("src/missing.ts");
|
||||
throw new Error("ENOENT: no such file or directory, open 'src/missing.ts'");
|
||||
},
|
||||
);
|
||||
|
||||
// when
|
||||
const parsed: unknown = JSON.parse(output);
|
||||
if (!isPostToolUseHookOutput(parsed)) throw new TypeError("Expected PostToolUse hook output");
|
||||
|
||||
// then
|
||||
expect(parsed.reason).toBe(
|
||||
"LSP diagnostics after editing src/missing.ts:\n\nENOENT: no such file or directory, open 'src/missing.ts'",
|
||||
);
|
||||
expect(parsed.hookSpecificOutput.additionalContext).toBe(parsed.reason);
|
||||
});
|
||||
});
|
||||
|
||||
interface PostToolUseHookOutput {
|
||||
readonly decision: "block";
|
||||
readonly reason: string;
|
||||
readonly hookSpecificOutput: {
|
||||
readonly hookEventName: "PostToolUse";
|
||||
readonly additionalContext: string;
|
||||
};
|
||||
}
|
||||
|
||||
function isPostToolUseHookOutput(value: unknown): value is PostToolUseHookOutput {
|
||||
if (!isRecord(value)) return false;
|
||||
const hookSpecificOutput = value["hookSpecificOutput"];
|
||||
return (
|
||||
value["decision"] === "block" &&
|
||||
typeof value["reason"] === "string" &&
|
||||
isRecord(hookSpecificOutput) &&
|
||||
hookSpecificOutput["hookEventName"] === "PostToolUse" &&
|
||||
typeof hookSpecificOutput["additionalContext"] === "string"
|
||||
);
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
Reference in New Issue
Block a user