Skip to content

Commit e134ab6

Browse files
committed
Improve robustness of empty message handling
1 parent a1761cb commit e134ab6

File tree

3 files changed

+30
-13
lines changed

3 files changed

+30
-13
lines changed

src/main.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,26 @@ ipcMain.handle(
213213
thinkingLevel,
214214
});
215215
try {
216-
// Early exit: empty message during streaming = interrupt
217-
// This allows Esc key to interrupt without creating empty user messages
218-
if (!message.trim() && aiService.isStreaming(workspaceId)) {
219-
log.debug("sendMessage handler: Empty message during streaming, interrupting");
220-
const stopResult = await aiService.stopStream(workspaceId);
221-
if (!stopResult.success) {
222-
log.error("Failed to stop stream:", stopResult.error);
223-
return {
224-
success: false,
225-
error: createUnknownSendMessageError(stopResult.error),
226-
};
216+
// Early exit: empty message = either interrupt (if streaming) or invalid input
217+
// This prevents race conditions where empty messages arrive after streaming stops
218+
if (!message.trim()) {
219+
// If streaming, this is an interrupt request (from Esc key)
220+
if (aiService.isStreaming(workspaceId)) {
221+
log.debug("sendMessage handler: Empty message during streaming, interrupting");
222+
const stopResult = await aiService.stopStream(workspaceId);
223+
if (!stopResult.success) {
224+
log.error("Failed to stop stream:", stopResult.error);
225+
return {
226+
success: false,
227+
error: createUnknownSendMessageError(stopResult.error),
228+
};
229+
}
230+
return { success: true };
227231
}
228-
return { success: true };
232+
233+
// If not streaming, reject empty message to prevent creating empty user messages
234+
log.debug("sendMessage handler: Rejected empty message (not streaming)");
235+
return { success: true }; // Return success to avoid error notification in UI
229236
}
230237

231238
// If editing, truncate history after the message being edited

src/services/streamManager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,10 @@ export class StreamManager extends EventEmitter {
307307
>();
308308

309309
for await (const part of streamInfo.streamResult.fullStream) {
310-
// Check if stream was cancelled
310+
// Check if stream was cancelled BEFORE processing any parts
311+
// This improves interruption responsiveness by catching aborts earlier
311312
if (streamInfo.abortController.signal.aborted) {
313+
log.debug("streamManager: Stream aborted, breaking from loop");
312314
break;
313315
}
314316

src/types/message.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ export function createCmuxMessage(
123123
: [];
124124
const parts = [...textPart, ...(additionalParts ?? [])];
125125

126+
// Validation: User messages must have at least one part with content
127+
// This prevents empty user messages from being created (defense-in-depth)
128+
if (role === "user" && parts.length === 0) {
129+
throw new Error(
130+
"Cannot create user message with no parts. Empty messages should be rejected upstream."
131+
);
132+
}
133+
126134
return {
127135
id,
128136
role,

0 commit comments

Comments
 (0)