fix(github): resolve PR review hanging in bundled app (#1793)

* fix(github): resolve PR review hanging in bundled app

Use getEffectiveSourcePath() and getConfiguredPythonPath() in
subprocess-runner.ts so the GitHub PR review runner correctly
locates the backend and Python executable in packaged Electron
builds — same pattern already used by title-generator and insights.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(github): remove dead code and update stale JSDoc

Address PR review findings:
- Remove unused fileURLToPath import, __filename and __dirname declarations
- Update getBackendPath() JSDoc to reflect new path resolution strategy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(github): guard getPythonPath managed env with isEnvReady check

Only use the managed Python path when pythonEnvManager.isEnvReady()
is true, preventing the bare 'python' fallback from
getConfiguredPythonPath() from being used when the managed env
isn't set up. The backendPath .venv fallback remains for dev mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-02-12 12:45:34 +01:00
committed by GitHub
parent 2823873566
commit 3791b37bbd
@@ -9,12 +9,7 @@ import { spawn, exec, execFile } from 'child_process';
import type { ChildProcess } from 'child_process'; import type { ChildProcess } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs'; import fs from 'fs';
// ESM-compatible __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
import type { Project } from '../../../../shared/types'; import type { Project } from '../../../../shared/types';
import type { AuthFailureInfo, BillingFailureInfo } from '../../../../shared/types/terminal'; import type { AuthFailureInfo, BillingFailureInfo } from '../../../../shared/types/terminal';
import { parsePythonCommand } from '../../../python-detector'; import { parsePythonCommand } from '../../../python-detector';
@@ -22,6 +17,8 @@ import { detectAuthFailure, detectBillingFailure } from '../../../rate-limit-det
import { getClaudeProfileManager } from '../../../claude-profile-manager'; import { getClaudeProfileManager } from '../../../claude-profile-manager';
import { getOperationRegistry, type OperationType } from '../../../claude-profile/operation-registry'; import { getOperationRegistry, type OperationType } from '../../../claude-profile/operation-registry';
import { isWindows, isMacOS } from '../../../platform'; import { isWindows, isMacOS } from '../../../platform';
import { getEffectiveSourcePath } from '../../../updater/path-resolver';
import { pythonEnvManager, getConfiguredPythonPath } from '../../../python-env-manager';
import { getTaskkillExePath, getWhereExePath } from '../../../utils/windows-paths'; import { getTaskkillExePath, getWhereExePath } from '../../../utils/windows-paths';
const execAsync = promisify(exec); const execAsync = promisify(exec);
@@ -479,10 +476,20 @@ export function runPythonSubprocess<T = unknown>(
} }
/** /**
* Get the Python path for a project's backend * Get the Python path for running GitHub runners.
* Cross-platform: uses Scripts/python.exe on Windows, bin/python on Unix *
* Prefers the managed Python environment (bundled app venv) when ready,
* falls back to project-local .venv for development repos.
*/ */
export function getPythonPath(backendPath: string): string { export function getPythonPath(backendPath: string): string {
// Use managed env when it's fully set up (has dependencies installed)
if (pythonEnvManager.isEnvReady()) {
const managed = getConfiguredPythonPath();
if (fs.existsSync(managed)) {
return managed;
}
}
// Fallback to venv in backend path (dev mode)
return isWindows() return isWindows()
? path.join(backendPath, '.venv', 'Scripts', 'python.exe') ? path.join(backendPath, '.venv', 'Scripts', 'python.exe')
: path.join(backendPath, '.venv', 'bin', 'python'); : path.join(backendPath, '.venv', 'bin', 'python');
@@ -498,44 +505,30 @@ export function getRunnerPath(backendPath: string): string {
/** /**
* Get the auto-claude backend path for a project * Get the auto-claude backend path for a project
* *
* Auto-detects the backend location using multiple strategies: * Uses getEffectiveSourcePath() which handles:
* 1. Development repo structure (apps/backend) * 1. User settings (autoBuildPath)
* 2. Electron app bundle location * 2. userData override (backend-source) for user-updated backend
* 3. Current working directory * 3. Bundled backend (process.resourcesPath/backend)
* 4. Development paths
* Falls back to project.path/apps/backend for development repos.
*/ */
export function getBackendPath(project: Project): string | null { export function getBackendPath(project: Project): string | null {
// Import app module for production path detection // Use shared path resolver which handles:
let app: any; // 1. User settings (autoBuildPath)
try { // 2. userData override (backend-source) for user-updated backend
app = require('electron').app; // 3. Bundled backend (process.resourcesPath/backend)
} catch { // 4. Development paths
// Electron not available in tests const effectivePath = getEffectiveSourcePath();
if (fs.existsSync(effectivePath) && fs.existsSync(path.join(effectivePath, 'runners', 'github', 'runner.py'))) {
return effectivePath;
} }
// Check if this is a development repo (has apps/backend structure) // Fallback: check project path for development repo structure
const appsBackendPath = path.join(project.path, 'apps', 'backend'); const appsBackendPath = path.join(project.path, 'apps', 'backend');
if (fs.existsSync(path.join(appsBackendPath, 'runners', 'github', 'runner.py'))) { if (fs.existsSync(path.join(appsBackendPath, 'runners', 'github', 'runner.py'))) {
return appsBackendPath; return appsBackendPath;
} }
// Auto-detect from app location (same logic as agent-process.ts)
const possiblePaths = [
// Dev mode: from dist/main -> ../../backend (apps/frontend/out/main -> apps/backend)
path.resolve(__dirname, '..', '..', '..', '..', '..', 'backend'),
// Alternative: from app root -> apps/backend
app ? path.resolve(app.getAppPath(), '..', 'backend') : null,
// If running from repo root with apps structure
path.resolve(process.cwd(), 'apps', 'backend'),
].filter((p): p is string => p !== null);
for (const backendPath of possiblePaths) {
// Check for runner.py as marker
const runnerPath = path.join(backendPath, 'runners', 'github', 'runner.py');
if (fs.existsSync(runnerPath)) {
return backendPath;
}
}
return null; return null;
} }