Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export class CodexAcpClient {
private pendingAccountUpdated: Promise<AccountUpdatedNotification> | null = null;
private readonly sessionNotificationQueues = new Map<string, Promise<void>>();
private skillExtraRoots: string[] = [];
private configPath: string | null = null;


constructor(codexClient: CodexAppServerClient, codexConfig?: JsonObject, modelProvider?: string) {
Expand All @@ -86,7 +87,7 @@ export class CodexAcpClient {
};

async initialize(request: acp.InitializeRequest): Promise<void> {
await this.codexClient.initialize({
const response = await this.codexClient.initialize({
capabilities: {
experimentalApi: true,
requestAttestation: false,
Expand All @@ -97,6 +98,11 @@ export class CodexAcpClient {
title: request.clientInfo?.title ?? this.defaultClientInfo.title,
}
});
this.configPath = response?.codexHome ?? null;
}

getHomePath(): string | null {
return this.configPath;
}

async authenticate(authRequest: acp.AuthenticateRequest): Promise<Boolean> {
Expand Down
4 changes: 4 additions & 0 deletions src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ export class CodexAcpServer {
await this.refreshSessionsAuthState(null);
throw RequestError.internalError(`${(e.message)}\n\nYou have been logged out. Please try again.`);
}
const configPath = this.codexAcpClient.getHomePath() ?? "global";
if (e.message.includes("load config")) {
throw RequestError.internalError(`${e.message}\n\nCheck ${configPath} and project .codex directories, especially their config.toml files, or any CODEX_CONFIG override.`);
}
}

private beginSessionOpen(sessionId: string): number {
Expand Down
4 changes: 3 additions & 1 deletion src/__tests__/CodexACPAgent/mcp-config-merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ url = "https://example.com/mcp"
await expect(codexAcpAgent.newSession({
cwd: "",
mcpServers: [conflictingMcp],
})).rejects.toThrow("url is not supported for stdio");
})).rejects.toMatchObject({
data: expect.stringContaining("url is not supported for stdio"),
});
});
});
18 changes: 18 additions & 0 deletions src/__tests__/CodexACPAgent/new-session-logout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ describe("New session logout handling", () => {
expect(logoutSpy).toHaveBeenCalledOnce();
});

it("includes the global config path in reload configuration errors", async () => {
const fixture = createCodexMockTestFixture();
const codexAcpAgent = fixture.getCodexAcpAgent();
const codexAcpClient = fixture.getCodexAcpClient();
const codexAppServerClient = fixture.getCodexAppServerClient();
vi.spyOn(codexAcpClient, "authRequired").mockResolvedValue(false);
const logoutSpy = vi.spyOn(codexAcpClient, "logout").mockResolvedValue();

const errorMessage = 'Internal error: "failed to reload config: filesystem path `/tmp` must be absolute, use `~/...`, or start with `:`"';
vi.spyOn(codexAppServerClient, "threadStart").mockRejectedValue(new Error(errorMessage));

expect(logoutSpy).toHaveBeenCalledTimes(0);
await expect(codexAcpAgent.newSession({cwd: "", mcpServers: []}))
.rejects.toMatchObject({
data: expect.stringContaining(`Check global and project .codex directories`),
});
});

it("refreshes OpenAI sessions when newSession error forces logout", async () => {
const fixture = createCodexMockTestFixture();
const codexAcpAgent = fixture.getCodexAcpAgent();
Expand Down