diff --git a/.aiox-core/core/orchestration/workflow-executor.js b/.aiox-core/core/orchestration/workflow-executor.js index 8ae22e2bb4..7eca0e0150 100644 --- a/.aiox-core/core/orchestration/workflow-executor.js +++ b/.aiox-core/core/orchestration/workflow-executor.js @@ -804,7 +804,7 @@ class WorkflowExecutor { '`wsl --install` (https://learn.microsoft.com/windows/wsl/install), ' + 'then install the CodeRabbit CLI inside the WSL distribution. ' + 'See docs/guides/installation-troubleshooting.md Issue 10. ' + - `To bypass this check, set coderabbit.installation_mode='native' in your config.` + 'To bypass this check, set coderabbit.installation_mode=\'native\' in your config.', ); } const wslPath = this.projectRoot @@ -829,7 +829,7 @@ class WorkflowExecutor { }); // Parse CodeRabbit output to extract issues - const issues = this.parseCodeRabbitOutput(stdout); + const issues = this.parseCodeRabbitOutput([stdout || '', stderr || ''].join('\n')); return { success: true, @@ -845,6 +845,16 @@ class WorkflowExecutor { issues: [], }; } + if (error.code !== undefined && error.code !== 0) { + return { + success: false, + error: + `CodeRabbit CLI exited with code ${error.code}. ` + + `stdout: ${(error.stdout || '').slice(0, 200)}, ` + + `stderr: ${(error.stderr || '').slice(0, 200)}`, + issues: [], + }; + } return { success: false, error: error.message, diff --git a/.aiox-core/core/quality-gates/layer2-pr-automation.js b/.aiox-core/core/quality-gates/layer2-pr-automation.js index 65e477757f..bbc540e528 100644 --- a/.aiox-core/core/quality-gates/layer2-pr-automation.js +++ b/.aiox-core/core/quality-gates/layer2-pr-automation.js @@ -147,6 +147,13 @@ class Layer2PRAutomation extends BaseLayer { } const result = await this.runCommand(command, timeout); + if (result.exitCode !== undefined && result.exitCode !== 0) { + throw new Error( + `CodeRabbit CLI exited with code ${result.exitCode}. ` + + `stdout: ${(result.stdout || '').slice(0, 200)}, ` + + `stderr: ${(result.stderr || '').slice(0, 200)}`, + ); + } // Parse CodeRabbit output for issues const issues = this.parseCodeRabbitOutput(result.stdout + result.stderr); diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index 99a43e2c78..7eb66fe093 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -8,7 +8,7 @@ # - File types for categorization # version: 5.2.7 -generated_at: "2026-05-18T11:40:45.332Z" +generated_at: "2026-05-18T15:42:34.055Z" generator: scripts/generate-install-manifest.js file_count: 1128 files: @@ -989,9 +989,9 @@ files: type: core size: 31469 - path: core/orchestration/workflow-executor.js - hash: sha256:5bc1c72b7565f3ae5794d4d379d2090c605ca6c383ebbb94a042c5f96b2b9102 + hash: sha256:8c56facc975f0452d686183d25ab1c19211afd127814b2ade963b4950352872f type: core - size: 38289 + size: 38673 - path: core/orchestration/workflow-orchestrator.js hash: sha256:82816bd5e6fecc9bbb77292444e53254c72bf93e5c04260784743354c6a58627 type: core @@ -1037,9 +1037,9 @@ files: type: core size: 9393 - path: core/quality-gates/layer2-pr-automation.js - hash: sha256:19e52369efe5cf0df9d0b6a9675dd555fdd46c1ab19cf6dc45d9173ad6e6524e + hash: sha256:40a7b03d6294c79741e9313ae91a8d6a30797dca1df4e3ca406edfe2911b4322 type: core - size: 11907 + size: 12213 - path: core/quality-gates/layer3-human-review.js hash: sha256:9bf79d5adfddae55d7dddfda777cd2775aa76f82204ddd0f660f6edbd093b16b type: core diff --git a/tests/core/orchestration/workflow-executor-coderabbit.test.js b/tests/core/orchestration/workflow-executor-coderabbit.test.js new file mode 100644 index 0000000000..1271fd9ac7 --- /dev/null +++ b/tests/core/orchestration/workflow-executor-coderabbit.test.js @@ -0,0 +1,134 @@ +/** + * WorkflowExecutor CodeRabbit invocation tests + * + * Regression guards for #763/#764: + * - non-zero CodeRabbit subprocess exits must not pass silently + * - workflow-executor WSL probing mirrors Layer 2 behavior + */ + +'use strict'; + +const childProcess = require('child_process'); +const os = require('os'); +const path = require('path'); + +const { WorkflowExecutor } = require('../../../.aiox-core/core/orchestration/workflow-executor'); + +const expectedDefaultCliPath = path.join(os.homedir(), '/.local/bin/coderabbit'); + +describe('WorkflowExecutor CodeRabbit analysis', () => { + let originalPlatform; + let spawnSyncSpy; + let execSpy; + + beforeEach(() => { + originalPlatform = process.platform; + spawnSyncSpy = jest.spyOn(childProcess, 'spawnSync').mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { status: 0, stdout: 'Ubuntu\n', stderr: '' }; + } + return { status: 0, stdout: '', stderr: '' }; + }); + execSpy = jest.spyOn(childProcess, 'exec').mockImplementation((command, options, callback) => { + callback(null, '', ''); + return { pid: 1234 }; + }); + }); + + afterEach(() => { + Object.defineProperty(process, 'platform', { value: originalPlatform }); + spawnSyncSpy.mockRestore(); + execSpy.mockRestore(); + }); + + const setPlatform = (value) => { + Object.defineProperty(process, 'platform', { value }); + }; + + const runAnalysis = (config = {}, projectRoot = process.cwd()) => { + const executor = new WorkflowExecutor(projectRoot, { saveState: false, debug: false }); + return executor.runCodeRabbitAnalysis({ + self_healing: { timeout_minutes: 1 }, + ...config, + }); + }; + + it('returns an informative failure when CodeRabbit exits non-zero', async () => { + setPlatform('darwin'); + execSpy.mockImplementation((command, options, callback) => { + const error = new Error('Command failed'); + error.code = 137; + error.stdout = ''; + error.stderr = ''; + callback(error, '', ''); + return { pid: 1234 }; + }); + + const result = await runAnalysis({ installation_mode: 'native' }); + + expect(result.success).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI exited with code 137/); + expect(result.error).toContain('stdout:'); + expect(result.error).toContain('stderr:'); + }); + + it('surfaces a clear error when WSL binary is missing (ENOENT)', async () => { + setPlatform('darwin'); + spawnSyncSpy.mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { error: Object.assign(new Error('ENOENT'), { code: 'ENOENT' }), status: null }; + } + return { status: 0, stdout: '', stderr: '' }; + }); + + const result = await runAnalysis({ installation_mode: 'wsl' }); + + expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object)); + expect(result.success).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI requires WSL/); + expect(result.error).toMatch(/wsl --install/); + expect(result.error).toMatch(/installation-troubleshooting/); + expect(execSpy).not.toHaveBeenCalled(); + }); + + it('surfaces a clear error when WSL has no usable distribution', async () => { + setPlatform('darwin'); + spawnSyncSpy.mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { status: 1, stdout: '', stderr: 'no distros\n' }; + } + return { status: 0, stdout: '', stderr: '' }; + }); + + const result = await runAnalysis({ installation_mode: 'wsl' }); + + expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object)); + expect(result.success).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI requires WSL/); + expect(execSpy).not.toHaveBeenCalled(); + }); + + it('does not probe WSL when installation_mode=native on Windows', async () => { + setPlatform('win32'); + + await runAnalysis({ installation_mode: 'native' }); + + expect(execSpy).toHaveBeenCalled(); + const command = execSpy.mock.calls[0][0]; + expect(command).toBe(`${expectedDefaultCliPath} --prompt-only -t uncommitted`); + const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl'); + expect(wslProbeCalls).toHaveLength(0); + }); + + it('does not probe WSL for default native mode on macOS', async () => { + setPlatform('darwin'); + + await runAnalysis(); + + expect(execSpy).toHaveBeenCalled(); + const command = execSpy.mock.calls[0][0]; + expect(command).toBe(`${expectedDefaultCliPath} --prompt-only -t uncommitted`); + const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl'); + expect(wslProbeCalls).toHaveLength(0); + }); +}); diff --git a/tests/unit/quality-gates/layer2-pr-automation.test.js b/tests/unit/quality-gates/layer2-pr-automation.test.js index 679454be25..7bc517ab60 100644 --- a/tests/unit/quality-gates/layer2-pr-automation.test.js +++ b/tests/unit/quality-gates/layer2-pr-automation.test.js @@ -127,6 +127,22 @@ describe('Layer2PRAutomation', () => { expect(result.skipped).toBe(true); expect(result.message).toContain('not installed'); }); + + it('should fail when CodeRabbit exits non-zero even without output', async () => { + layer.runCommand = jest.fn().mockResolvedValue({ + exitCode: 137, + stdout: '', + stderr: '', + duration: 100, + }); + + const result = await layer.runCodeRabbit(); + + expect(result.pass).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI exited with code 137/); + expect(result.error).toContain('stdout:'); + expect(result.error).toContain('stderr:'); + }); }); describe('runQuinnReview', () => {