Skip to content

Commit df30cbc

Browse files
authored
🤖 fix: use ResultSchema for sendMessage output to prevent field stripping (#773)
## Summary - Fix workspace creation failing with "Unexpected response from server" due to Zod schema union ordering - `z.union()` was matching `ResultSchema(z.void())` first, which stripped `workspaceId`/`metadata` as "extra keys" - Replaced with single `ResultSchema` using optional fields for consistent response shape ## Test plan - [x] `make typecheck` passes - [x] `useCreationWorkspace.test.tsx` tests pass - [x] Manual test: create new workspace from scratch - should navigate to workspace instead of showing error toast _Generated with `mux`_ Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent 41c77ef commit df30cbc

File tree

6 files changed

+31
-31
lines changed

6 files changed

+31
-31
lines changed

‎src/browser/components/ChatInput/useCreationWorkspace.test.tsx‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,16 @@ const setupWindow = ({ listBranches, sendMessage }: SetupWindowOptions = {}) =>
138138
if (!args.workspaceId) {
139139
return Promise.resolve({
140140
success: true,
141-
workspaceId: TEST_WORKSPACE_ID,
142-
metadata: TEST_METADATA,
141+
data: {
142+
workspaceId: TEST_WORKSPACE_ID,
143+
metadata: TEST_METADATA,
144+
},
143145
} satisfies WorkspaceSendMessageResult);
144146
}
145147

146148
const existingWorkspaceResult: WorkspaceSendMessageResult = {
147149
success: true,
148-
data: undefined,
150+
data: {},
149151
};
150152
return Promise.resolve(existingWorkspaceResult);
151153
});
@@ -357,8 +359,10 @@ describe("useCreationWorkspace", () => {
357359
(_args: WorkspaceSendMessageArgs): Promise<WorkspaceSendMessageResult> =>
358360
Promise.resolve({
359361
success: true as const,
360-
workspaceId: TEST_WORKSPACE_ID,
361-
metadata: TEST_METADATA,
362+
data: {
363+
workspaceId: TEST_WORKSPACE_ID,
364+
metadata: TEST_METADATA,
365+
},
362366
})
363367
);
364368
const { workspaceApi } = setupWindow({

‎src/browser/components/ChatInput/useCreationWorkspace.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,17 @@ export function useCreationWorkspace({
128128
return false;
129129
}
130130

131-
// Check if this is a workspace creation result (has metadata field)
132-
if ("metadata" in result && result.metadata) {
133-
syncCreationPreferences(projectPath, result.metadata.id);
131+
// Check if this is a workspace creation result (has metadata in data)
132+
const { metadata } = result.data;
133+
if (metadata) {
134+
syncCreationPreferences(projectPath, metadata.id);
134135
if (projectPath) {
135136
const pendingInputKey = getInputKey(getPendingScopeId(projectPath));
136137
updatePersistedState(pendingInputKey, "");
137138
}
138139
// Settings are already persisted via useDraftWorkspaceSettings
139140
// Notify parent to switch workspace (clears input via parent unmount)
140-
onWorkspaceCreated(result.metadata);
141+
onWorkspaceCreated(metadata);
141142
setIsSending(false);
142143
return true;
143144
} else {

‎src/common/orpc/schemas/api.ts‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,13 @@ export const workspace = {
167167
trunkBranch: z.string().optional(),
168168
}).optional(),
169169
}),
170-
output: z.union([
171-
ResultSchema(z.void(), SendMessageErrorSchema),
170+
output: ResultSchema(
172171
z.object({
173-
success: z.literal(true),
174-
workspaceId: z.string(),
175-
metadata: FrontendWorkspaceMetadataSchema,
172+
workspaceId: z.string().optional(),
173+
metadata: FrontendWorkspaceMetadataSchema.optional(),
176174
}),
177-
]),
175+
SendMessageErrorSchema
176+
),
178177
},
179178
resumeStream: {
180179
input: z.object({

‎src/node/orpc/router.ts‎

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -199,35 +199,30 @@ export const router = (authToken?: string) => {
199199
const result = await context.workspaceService.sendMessage(
200200
input.workspaceId,
201201
input.message,
202-
input.options // Cast to avoid Zod vs Interface mismatch
202+
input.options
203203
);
204204

205-
// Type mismatch handling: WorkspaceService returns Result<any>.
206-
// Our schema output is ResultSchema(void, SendMessageError) OR {success:true, ...}
207-
// We need to ensure strict type alignment.
208205
if (!result.success) {
209206
const error =
210207
typeof result.error === "string"
211208
? { type: "unknown" as const, raw: result.error }
212209
: result.error;
213210
return { success: false, error };
214211
}
215-
// If success, it returns different shapes depending on lazy creation.
216-
// If lazy creation happened, it returns {success: true, workspaceId, metadata}
217-
// If regular message, it returns {success: true, data: ...} -> wait, result.data is undefined for normal message?
218-
// SendMessage in AgentSession returns Result<void> mostly.
219-
// But createForFirstMessage returns object.
220212

221-
// Check result shape
213+
// Check if this is a workspace creation result
222214
if ("workspaceId" in result) {
223215
return {
224216
success: true,
225-
workspaceId: result.workspaceId,
226-
metadata: result.metadata,
217+
data: {
218+
workspaceId: result.workspaceId,
219+
metadata: result.metadata,
220+
},
227221
};
228222
}
229223

230-
return { success: true, data: undefined };
224+
// Regular message send (no workspace creation)
225+
return { success: true, data: {} };
231226
}),
232227
resumeStream: t
233228
.input(schemas.workspace.resumeStream.input)

‎src/node/services/workspaceService.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ export class WorkspaceService extends EventEmitter {
453453

454454
const allMetadata = await this.config.getAllWorkspaceMetadata();
455455
const completeMetadata = allMetadata.find((m) => m.id === workspaceId);
456+
456457
if (!completeMetadata) {
457458
return { success: false, error: "Failed to retrieve workspace metadata" };
458459
}

‎tests/integration/helpers.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@ export async function sendMessage(
132132
return { success: false, error: { type: "unknown", raw } };
133133
}
134134

135-
if (result.success && "workspaceId" in result) {
136-
// Lazy workspace creation path returns metadata/workspaceId; normalize to void success for callers
135+
// Normalize to Result<void> for callers - they just care about success/failure
136+
if (result.success) {
137137
return { success: true, data: undefined };
138138
}
139139

140-
return result;
140+
return { success: false, error: result.error };
141141
}
142142

143143
/**

0 commit comments

Comments
 (0)