Skip to content

Commit b96328a

Browse files
committed
🤖 fix: surface sendMessage errors to chat UI during workspace creation
When workspace creation background operations fail (e.g., API key not configured), errors were silently logged to console with no user feedback. The user would just see their message with no response. Changes: - Add formatSendMessageError helper to convert SendMessageError to user-friendly messages with appropriate StreamErrorType - Handle sendMessage result in completeWorkspaceCreation and emit stream-error events on failure - Add unit tests for the new helper This ensures users see error messages in the chat UI instead of silence. _Generated with mux_
1 parent 96d2928 commit b96328a

File tree

3 files changed

+121
-3
lines changed

3 files changed

+121
-3
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { formatSendMessageError, createUnknownSendMessageError } from "./sendMessageError";
3+
4+
describe("formatSendMessageError", () => {
5+
test("formats api_key_not_found with authentication errorType", () => {
6+
const result = formatSendMessageError({
7+
type: "api_key_not_found",
8+
provider: "anthropic",
9+
});
10+
11+
expect(result.errorType).toBe("authentication");
12+
expect(result.message).toContain("anthropic");
13+
expect(result.message).toContain("API key");
14+
});
15+
16+
test("formats provider_not_supported", () => {
17+
const result = formatSendMessageError({
18+
type: "provider_not_supported",
19+
provider: "unsupported-provider",
20+
});
21+
22+
expect(result.errorType).toBe("unknown");
23+
expect(result.message).toContain("unsupported-provider");
24+
expect(result.message).toContain("not supported");
25+
});
26+
27+
test("formats invalid_model_string with model_not_found errorType", () => {
28+
const result = formatSendMessageError({
29+
type: "invalid_model_string",
30+
message: "Invalid model format: foo",
31+
});
32+
33+
expect(result.errorType).toBe("model_not_found");
34+
expect(result.message).toBe("Invalid model format: foo");
35+
});
36+
37+
test("formats incompatible_workspace", () => {
38+
const result = formatSendMessageError({
39+
type: "incompatible_workspace",
40+
message: "Workspace is incompatible",
41+
});
42+
43+
expect(result.errorType).toBe("unknown");
44+
expect(result.message).toBe("Workspace is incompatible");
45+
});
46+
47+
test("formats unknown errors", () => {
48+
const result = formatSendMessageError({
49+
type: "unknown",
50+
raw: "Something went wrong",
51+
});
52+
53+
expect(result.errorType).toBe("unknown");
54+
expect(result.message).toBe("Something went wrong");
55+
});
56+
});
57+
58+
describe("createUnknownSendMessageError", () => {
59+
test("creates unknown error with trimmed message", () => {
60+
const result = createUnknownSendMessageError(" test error ");
61+
62+
expect(result).toEqual({ type: "unknown", raw: "test error" });
63+
});
64+
65+
test("throws on empty message", () => {
66+
expect(() => createUnknownSendMessageError("")).toThrow();
67+
expect(() => createUnknownSendMessageError(" ")).toThrow();
68+
});
69+
});

src/node/services/utils/sendMessageError.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "@/common/utils/assert";
2-
import type { SendMessageError } from "@/common/types/errors";
2+
import type { SendMessageError, StreamErrorType } from "@/common/types/errors";
33

44
/**
55
* Helper to wrap arbitrary errors into SendMessageError structures.
@@ -15,3 +15,39 @@ export const createUnknownSendMessageError = (raw: string): SendMessageError =>
1515
raw: trimmed,
1616
};
1717
};
18+
19+
/**
20+
* Formats a SendMessageError into a user-visible message and StreamErrorType
21+
* for display in the chat UI as a stream-error event.
22+
*/
23+
export const formatSendMessageError = (
24+
error: SendMessageError
25+
): { message: string; errorType: StreamErrorType } => {
26+
switch (error.type) {
27+
case "api_key_not_found":
28+
return {
29+
message: `API key not configured for ${error.provider}. Please add your API key in settings.`,
30+
errorType: "authentication",
31+
};
32+
case "provider_not_supported":
33+
return {
34+
message: `Provider "${error.provider}" is not supported.`,
35+
errorType: "unknown",
36+
};
37+
case "invalid_model_string":
38+
return {
39+
message: error.message,
40+
errorType: "model_not_found",
41+
};
42+
case "incompatible_workspace":
43+
return {
44+
message: error.message,
45+
errorType: "unknown",
46+
};
47+
case "unknown":
48+
return {
49+
message: error.raw,
50+
errorType: "unknown",
51+
};
52+
}
53+
};

src/node/services/workspaceService.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import { listLocalBranches, detectDefaultTrunkBranch } from "@/node/git";
1616
import { createRuntime, IncompatibleRuntimeError } from "@/node/runtime/runtimeFactory";
1717
import { generateWorkspaceName, generatePlaceholderName } from "./workspaceTitleGenerator";
1818
import { validateWorkspaceName } from "@/common/utils/validation/workspaceValidation";
19+
import { formatSendMessageError } from "@/node/services/utils/sendMessageError";
1920

2021
import type {
2122
SendMessageOptions,
2223
DeleteMessage,
2324
ImagePart,
2425
WorkspaceChatMessage,
26+
StreamErrorMessage,
2527
} from "@/common/orpc/types";
2628
import type { SendMessageError } from "@/common/types/errors";
2729
import type {
@@ -606,8 +608,19 @@ export class WorkspaceService extends EventEmitter {
606608
initLogger.logComplete(-1);
607609
});
608610

609-
// Send the first message
610-
void session.sendMessage(message, options);
611+
// Send the first message, surfacing errors to the chat UI
612+
void session.sendMessage(message, options).then((result) => {
613+
if (!result.success) {
614+
const { message: errorMessage, errorType } = formatSendMessageError(result.error);
615+
const streamError: StreamErrorMessage = {
616+
type: "stream-error",
617+
messageId: `error-${Date.now()}`,
618+
error: errorMessage,
619+
errorType,
620+
};
621+
session.emitChatEvent(streamError);
622+
}
623+
});
611624

612625
// Generate AI name asynchronously and rename if successful
613626
void this.generateAndApplyAIName(workspaceId, message, finalBranchName, options.model);

0 commit comments

Comments
 (0)