Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .aiox-core/core/orchestration/workflow-executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,9 +775,9 @@ class WorkflowExecutor {
*/
async runCodeRabbitAnalysis(coderabbitConfig) {
try {
const { exec } = require('child_process');
const childProcess = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const execAsync = promisify(childProcess.exec);

// Build command for current platform.
// - Explicit installation_mode: 'wsl' | 'native' wins (lets ops override).
Expand All @@ -793,6 +793,20 @@ class WorkflowExecutor {
(process.platform === 'win32' ? 'wsl' : 'native');
let command;
if (mode === 'wsl') {
// Probe WSL availability before building the command. Gives a clearer
// diagnostic than cmd.exe's generic "'wsl' is not recognized" when WSL
// is not installed. ENOENT (binary missing) and non-zero exit (WSL
// feature present but no distribution installed) both fail this check.
const wslProbe = childProcess.spawnSync('wsl', ['-l'], { encoding: 'utf8' });
if (wslProbe.error || wslProbe.status !== 0) {
throw new Error(
'CodeRabbit CLI requires WSL on Windows hosts. Install WSL via ' +
'`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.`
);
}
const wslPath = this.projectRoot
.replace(/^([A-Za-z]):/, (_, drive) => `/mnt/${drive.toLowerCase()}`)
.replace(/\\/g, '/');
Expand Down
19 changes: 17 additions & 2 deletions .aiox-core/core/quality-gates/layer2-pr-automation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* @story 2.10 - Quality Gate Manager
*/

const { spawn } = require('child_process');
const childProcess = require('child_process');
const fs = require('fs').promises;
const os = require('os');
const path = require('path');
Expand Down Expand Up @@ -117,6 +117,21 @@ class Layer2PRAutomation extends BaseLayer {
this.coderabbit.installation_mode ||
(process.platform === 'win32' ? 'wsl' : 'native');
if (mode === 'wsl') {
// Probe WSL availability before building the command. Gives a clearer
// diagnostic than cmd.exe's generic "'wsl' is not recognized" when
// WSL is not installed. ENOENT (binary missing) and non-zero exit
// (WSL feature present but no distribution installed) both fail this
// check.
const wslProbe = childProcess.spawnSync('wsl', ['-l'], { encoding: 'utf8' });
if (wslProbe.error || wslProbe.status !== 0) {
throw new Error(
'CodeRabbit CLI requires WSL on Windows hosts. Install WSL via ' +
'`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.',
);
}
const projectRoot = this.coderabbit.projectRoot || process.cwd();
const wslProjectPath = projectRoot
.replace(/\\/g, '/')
Expand Down Expand Up @@ -322,7 +337,7 @@ class Layer2PRAutomation extends BaseLayer {
env: { ...process.env },
};

const child = spawn(command, [], options);
const child = childProcess.spawn(command, [], options);

let stdout = '';
let stderr = '';
Expand Down
10 changes: 5 additions & 5 deletions .aiox-core/install-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - File types for categorization
#
version: 5.2.7
generated_at: "2026-05-18T05:37:21.641Z"
generated_at: "2026-05-18T11:40:45.332Z"
generator: scripts/generate-install-manifest.js
file_count: 1128
files:
Expand Down Expand Up @@ -989,9 +989,9 @@ files:
type: core
size: 31469
- path: core/orchestration/workflow-executor.js
hash: sha256:ddc880683df489ec7203ab7ecd8e066a08e2f72b011fe0dfe578d31155eb513b
hash: sha256:5bc1c72b7565f3ae5794d4d379d2090c605ca6c383ebbb94a042c5f96b2b9102
type: core
size: 37348
size: 38289
- path: core/orchestration/workflow-orchestrator.js
hash: sha256:82816bd5e6fecc9bbb77292444e53254c72bf93e5c04260784743354c6a58627
type: core
Expand Down Expand Up @@ -1037,9 +1037,9 @@ files:
type: core
size: 9393
- path: core/quality-gates/layer2-pr-automation.js
hash: sha256:53e1f05e3ece4731848ad818f13a97ce18f553327b6f2e385e4d299492cc890a
hash: sha256:19e52369efe5cf0df9d0b6a9675dd555fdd46c1ab19cf6dc45d9173ad6e6524e
type: core
size: 10923
size: 11907
- path: core/quality-gates/layer3-human-review.js
hash: sha256:9bf79d5adfddae55d7dddfda777cd2775aa76f82204ddd0f660f6edbd093b16b
type: core
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/quality-gates/cross-platform-coderabbit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

const os = require('os');
const path = require('path');
const childProcess = require('child_process');

const { Layer2PRAutomation } = require('../../../.aiox-core/core/quality-gates/layer2-pr-automation');

Expand All @@ -27,11 +28,21 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => {
let originalCwd;
let layer;
let capturedCommand;
let spawnSyncSpy;

beforeEach(() => {
originalPlatform = process.platform;
originalCwd = process.cwd;
capturedCommand = null;
// Mock the WSL availability probe added for #757 so legacy command-shape
// tests don't fail on macOS/Linux dev boxes where `wsl` isn't installed.
// Tests covering the probe failure path override this on a per-test basis.
spawnSyncSpy = jest.spyOn(childProcess, 'spawnSync').mockImplementation((cmd) => {
if (cmd === 'wsl') {
return { status: 0, stdout: 'Ubuntu\n', stderr: '' };
}
return { status: 0, stdout: '', stderr: '' };
});
layer = new Layer2PRAutomation({
enabled: true,
coderabbit: { enabled: true },
Expand All @@ -47,6 +58,7 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => {
afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform });
process.cwd = originalCwd;
spawnSyncSpy.mockRestore();
});

const setPlatform = (value) => {
Expand Down Expand Up @@ -148,4 +160,53 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => {
// that WSL cannot resolve.
expect(capturedCommand).toContain('~/custom/coderabbit --prompt-only -t uncommitted');
});

// Issue #757 — WSL availability probe before invoking CodeRabbit on Windows
describe('WSL availability probe (Issue #757)', () => {
it('surfaces a clear error when WSL binary is missing (ENOENT)', async () => {
setPlatform('win32');
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 layer.runCodeRabbit();
expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object));
expect(result.pass).toBe(false);
expect(result.error).toMatch(/CodeRabbit CLI requires WSL/);
expect(result.error).toMatch(/wsl --install/);
expect(result.error).toMatch(/installation-troubleshooting/);
});

it('surfaces a clear error when WSL is installed but has no distribution (exit != 0)', async () => {
setPlatform('win32');
spawnSyncSpy.mockImplementation((cmd) => {
if (cmd === 'wsl') {
return { status: 1, stdout: '', stderr: 'no distros\n' };
}
return { status: 0, stdout: '', stderr: '' };
});
const result = await layer.runCodeRabbit();
expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object));
expect(result.pass).toBe(false);
expect(result.error).toMatch(/CodeRabbit CLI requires WSL/);
});

it('does NOT probe WSL when installation_mode=native on Windows', async () => {
setPlatform('win32');
layer.coderabbit.installation_mode = 'native';
await layer.runCodeRabbit();
// The probe MUST NOT have been called when native mode is explicit.
const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl');
expect(wslProbeCalls).toHaveLength(0);
});

it('does NOT probe WSL on macOS native invocation', async () => {
setPlatform('darwin');
await layer.runCodeRabbit();
const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl');
expect(wslProbeCalls).toHaveLength(0);
});
});
});
Loading