Skip to content

Commit 76234c1

Browse files
Merge branch 'main' into NONE-fix-env-var-for-jir-coding-agent
2 parents 46f2603 + 7e4d7b9 commit 76234c1

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Bug Fixes
66

7+
- **RovoDev**: Fixed `TypeError: terminated` from Node.js undici being incorrectly surfaced as an error dialog when aborting an in-flight chat request. The error is now silently handled as a normal abort, preventing spurious error messages and noisy telemetry — particularly in Boysenberry mode where long-running YOLO streams make mid-stream aborts more common.
8+
79
- Fixed shell command injection vulnerability (VULN-1825192) in git operations. The `Shell` utility class now uses `shell: false` when spawning processes, and all git commands pass arguments as separate array elements rather than interpolating user-controlled values (e.g. branch names, file paths, commit hashes) directly into shell command strings. This prevents Remote Code Execution via maliciously crafted git branch names.
810

911
## What's new in 4.0.29

src/rovo-dev/rovoDevChatProvider.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,19 @@ describe('RovoDevChatProvider', () => {
464464

465465
expect(chatProvider['_lastMessageSentTime']).toBeUndefined();
466466
});
467+
468+
it('should clear pending deferred request on cancellation so next prompt is sent as plain message', async () => {
469+
await chatProvider.setReady(mockApiClient);
470+
471+
// Simulate a pending deferred tool call (e.g. ask_user_questions)
472+
chatProvider['_pendingDeferredRequest'] = 'deferred-tool-call-123';
473+
474+
mockApiClient.cancel.mockResolvedValue({ cancelled: true, message: 'Cancelled' });
475+
476+
await chatProvider.executeCancel(false);
477+
478+
expect(chatProvider['_pendingDeferredRequest']).toBeUndefined();
479+
});
467480
});
468481

469482
describe('signalToolRequestChoiceSubmit', () => {
@@ -1044,5 +1057,53 @@ describe('RovoDevChatProvider', () => {
10441057
}),
10451058
);
10461059
});
1060+
1061+
it('should handle undici TypeError: terminated as an abort (no error dialog)', async () => {
1062+
// Simulates the Node.js undici error thrown when AbortController.abort() is called
1063+
// on an in-flight fetch. undici throws a plain TypeError with message "terminated"
1064+
// rather than an AbortError, so it must be caught explicitly.
1065+
const terminatedError = new TypeError('terminated');
1066+
mockApiClient.chat.mockRejectedValue(terminatedError);
1067+
1068+
const mockPrompt: RovoDevPrompt = {
1069+
text: 'test prompt',
1070+
context: [],
1071+
};
1072+
1073+
await chatProvider.executeChat(mockPrompt, []);
1074+
1075+
// Should NOT show an error dialog — terminated is a normal abort side-effect
1076+
expect(mockWebview.postMessage).not.toHaveBeenCalledWith(
1077+
expect.objectContaining({
1078+
type: RovoDevProviderMessageType.ShowDialog,
1079+
}),
1080+
);
1081+
1082+
// Should send CompleteMessage so the UI returns to an interactive state
1083+
expect(mockWebview.postMessage).toHaveBeenCalledWith(
1084+
expect.objectContaining({
1085+
type: RovoDevProviderMessageType.CompleteMessage,
1086+
}),
1087+
);
1088+
});
1089+
1090+
it('should still show error dialog for other TypeErrors (not terminated)', async () => {
1091+
// Ensures the fix is narrowly scoped and doesn't swallow real TypeErrors
1092+
const otherTypeError = new TypeError('Failed to fetch');
1093+
mockApiClient.chat.mockRejectedValue(otherTypeError);
1094+
1095+
const mockPrompt: RovoDevPrompt = {
1096+
text: 'test prompt',
1097+
context: [],
1098+
};
1099+
1100+
await chatProvider.executeChat(mockPrompt, []);
1101+
1102+
expect(mockWebview.postMessage).toHaveBeenCalledWith(
1103+
expect.objectContaining({
1104+
type: RovoDevProviderMessageType.ShowDialog,
1105+
}),
1106+
);
1107+
});
10471108
});
10481109
});

src/rovo-dev/rovoDevChatProvider.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,12 @@ export class RovoDevChatProvider {
326326
public async executeCancel(fromNewSession: boolean): Promise<boolean> {
327327
const webview = this._webView!;
328328

329+
// Clear any pending deferred tool call so the next user prompt is sent
330+
// as a plain message instead of a tool-call response. Without this the
331+
// backend would reject the prompt with "Cannot provide a new user prompt
332+
// when the message history contains unprocessed tool calls".
333+
this._pendingDeferredRequest = undefined;
334+
329335
let success: boolean;
330336
if (this._rovoDevApiClient) {
331337
if (this._pendingCancellation) {
@@ -967,7 +973,7 @@ export class RovoDevChatProvider {
967973
try {
968974
await func(this._rovoDevApiClient);
969975
} catch (error) {
970-
if (error.name === 'AbortError') {
976+
if (error.name === 'AbortError' || (error instanceof TypeError && error.message === 'terminated')) {
971977
await webview.postMessage({
972978
type: RovoDevProviderMessageType.CompleteMessage,
973979
promptId: this._currentPromptId,

0 commit comments

Comments
 (0)