Skip to content

Commit 8b24b01

Browse files
committed
🤖 fix: fail-fast workspace title generation with provider errors
- Revert fallback chat-{timestamp} behavior - generateWorkspaceName now throws SendMessageError-compatible objects - IpcMain maps errors to Result<void, SendMessageError> so UI shows provider toasts _Generated with `mux`_
1 parent 5d553f9 commit 8b24b01

File tree

2 files changed

+42
-24
lines changed

2 files changed

+42
-24
lines changed

src/node/services/ipcMain.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { SUPPORTED_PROVIDERS } from "@/common/constants/providers";
1818
import { DEFAULT_RUNTIME_CONFIG } from "@/common/constants/workspace";
1919
import type { SendMessageError } from "@/common/types/errors";
2020
import type { SendMessageOptions, DeleteMessage, ImagePart } from "@/common/types/ipc";
21-
import { Ok, Err } from "@/common/types/result";
21+
import { Ok, Err, type Result } from "@/common/types/result";
2222
import { validateWorkspaceName } from "@/common/utils/validation/workspaceValidation";
2323
import type { WorkspaceMetadata, FrontendWorkspaceMetadata } from "@/common/types/workspace";
2424
import { createBashTool } from "@/node/services/tools/bash";
@@ -168,11 +168,22 @@ export class IpcMain {
168168
}
169169
): Promise<
170170
| { success: true; workspaceId: string; metadata: FrontendWorkspaceMetadata }
171-
| { success: false; error: string }
171+
| Result<void, SendMessageError>
172172
> {
173173
try {
174174
// 1. Generate workspace branch name using AI (use same model as message)
175-
const branchName = await generateWorkspaceName(message, options.model, this.config);
175+
let branchName: string;
176+
try {
177+
branchName = await generateWorkspaceName(message, options.model, this.config);
178+
} catch (e) {
179+
// Surface provider error types to the renderer, matching sendMessage behavior
180+
const err = e as unknown;
181+
if (err && typeof err === "object" && "type" in (err as Record<string, unknown>)) {
182+
return Err(err as SendMessageError);
183+
}
184+
const msg = err instanceof Error ? err.message : String(err);
185+
return Err({ type: "unknown", raw: `Failed to generate workspace name: ${msg}` });
186+
}
176187

177188
log.debug("Generated workspace name", { branchName });
178189

@@ -205,7 +216,7 @@ export class IpcMain {
205216
}
206217
} catch (error) {
207218
const errorMsg = error instanceof Error ? error.message : String(error);
208-
return { success: false, error: errorMsg };
219+
return Err({ type: "unknown", raw: `Failed to prepare runtime: ${errorMsg}` });
209220
}
210221

211222
const session = this.getOrCreateSession(workspaceId);
@@ -222,7 +233,7 @@ export class IpcMain {
222233
});
223234

224235
if (!createResult.success || !createResult.workspacePath) {
225-
return { success: false, error: createResult.error ?? "Failed to create workspace" };
236+
return Err({ type: "unknown", raw: createResult.error ?? "Failed to create workspace" });
226237
}
227238

228239
const projectName =
@@ -255,7 +266,7 @@ export class IpcMain {
255266
const allMetadata = await this.config.getAllWorkspaceMetadata();
256267
const completeMetadata = allMetadata.find((m) => m.id === workspaceId);
257268
if (!completeMetadata) {
258-
return { success: false, error: "Failed to retrieve workspace metadata" };
269+
return Err({ type: "unknown", raw: "Failed to retrieve workspace metadata" });
259270
}
260271

261272
session.emitMetadata(completeMetadata);
@@ -286,7 +297,7 @@ export class IpcMain {
286297
} catch (error) {
287298
const errorMessage = error instanceof Error ? error.message : String(error);
288299
log.error("Unexpected error in createWorkspaceForFirstMessage:", error);
289-
return { success: false, error: `Failed to create workspace: ${errorMessage}` };
300+
return Err({ type: "unknown", raw: `Failed to create workspace: ${errorMessage}` });
290301
}
291302
}
292303

src/node/services/workspaceTitleGenerator.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ const workspaceNameSchema = z.object({
1616
});
1717

1818
/**
19-
* Generate workspace name using AI
20-
* Falls back to timestamp-based name if AI generation fails
21-
* @param message - The user's first message
22-
* @param modelString - Model string from send message options (e.g., "anthropic:claude-3-5-sonnet-20241022")
23-
* @param config - Config instance for provider access
19+
* Generate workspace name using AI.
20+
* If AI cannot be used (e.g. missing credentials, unsupported provider, invalid model),
21+
* this function throws a SendMessageError-compatible object so callers can surface
22+
* the same provider error UX used elsewhere in the app.
2423
*/
2524
export async function generateWorkspaceName(
2625
message: string,
@@ -31,27 +30,34 @@ export async function generateWorkspaceName(
3130
const model = getModelForTitleGeneration(modelString, config);
3231

3332
if (!model) {
34-
// No providers available, use fallback immediately
35-
return createFallbackName();
33+
// No usable model — infer error from modelString + providers config like sendMessage does
34+
const [providerName, modelId] = modelString.split(":", 2);
35+
if (!providerName || !modelId) {
36+
throw { type: "invalid_model_string", message: `Invalid model string format: "${modelString}". Expected "provider:model-id"` };
37+
}
38+
const providers = config.loadProvidersConfig();
39+
const hasProvider = Boolean(providers && providers[providerName]);
40+
if (!hasProvider || !providers?.[providerName]?.apiKey) {
41+
throw { type: "api_key_not_found", provider: providerName };
42+
}
43+
throw { type: "provider_not_supported", provider: providerName };
3644
}
3745

3846
const result = await generateObject({
3947
model,
4048
schema: workspaceNameSchema,
41-
prompt: `Generate a git-safe branch/workspace name for this development task:
42-
43-
"${message}"
44-
45-
Requirements:
46-
- Git-safe identifier (e.g., "automatic-title-generation")
47-
- Lowercase, hyphens only, no spaces
48-
- Concise (2-5 words) and descriptive of the task`,
49+
prompt: `Generate a git-safe branch/workspace name for this development task:\n\n"${message}"\n\nRequirements:\n- Git-safe identifier (e.g., "automatic-title-generation")\n- Lowercase, hyphens only, no spaces\n- Concise (2-5 words) and descriptive of the task`,
4950
});
5051

5152
return validateBranchName(result.object.name);
5253
} catch (error) {
53-
log.error("Failed to generate workspace name with AI, using fallback", error);
54-
return createFallbackName();
54+
// If error is already a structured SendMessageError, rethrow as-is
55+
if (error && typeof error === "object" && "type" in (error as Record<string, unknown>)) {
56+
throw error;
57+
}
58+
const messageText = error instanceof Error ? error.message : String(error);
59+
log.error("Failed to generate workspace name with AI", error);
60+
throw { type: "unknown", raw: `Failed to generate workspace name: ${messageText}` };
5561
}
5662
}
5763

@@ -115,6 +121,7 @@ function getModelForTitleGeneration(modelString: string, config: Config): Langua
115121

116122
/**
117123
* Create fallback name using timestamp
124+
* NOTE: Not used by current flow; kept for potential future use.
118125
*/
119126
function createFallbackName(): string {
120127
const timestamp = Date.now().toString(36);

0 commit comments

Comments
 (0)