diff --git a/.gitignore b/.gitignore index fa5bcb63..7fe87369 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,7 @@ lerna-debug.log* .auto-claude-status .claude_settings.json .update-metadata.json +/config.json # =========================== # Python (apps/backend) diff --git a/apps/frontend/scripts/download-python.cjs b/apps/frontend/scripts/download-python.cjs index 6c48dc89..86bc47b3 100644 --- a/apps/frontend/scripts/download-python.cjs +++ b/apps/frontend/scripts/download-python.cjs @@ -702,9 +702,32 @@ async function downloadPython(targetPlatform, targetArch, options = {}) { try { const version = verifyPythonBinary(pythonBin); console.log(`[download-python] Verified: ${version}`); - return { success: true, pythonPath: pythonBin, sitePackagesPath: sitePackagesDir }; - } catch { - console.log(`[download-python] Existing installation is broken, re-downloading...`); + + // Verify critical packages exist (fixes GitHub issue #416) + // Without this check, corrupted caches with missing packages would be accepted + // Note: Same list exists in python-env-manager.ts - keep them in sync + // This validation assumes traditional Python packages with __init__.py (not PEP 420 namespace packages) + const criticalPackages = ['claude_agent_sdk', 'dotenv']; + const missingPackages = criticalPackages.filter(pkg => { + const pkgPath = path.join(sitePackagesDir, pkg); + // Check both directory and __init__.py for more robust validation + const initFile = path.join(pkgPath, '__init__.py'); + return !fs.existsSync(pkgPath) || !fs.existsSync(initFile); + }); + + if (missingPackages.length > 0) { + console.log(`[download-python] Critical packages missing or incomplete: ${missingPackages.join(', ')}`); + console.log(`[download-python] Reinstalling packages...`); + // Remove site-packages to force reinstall, keep Python binary + // Flow continues below to re-install packages (skipPackages check at line 794) + fs.rmSync(sitePackagesDir, { recursive: true, force: true }); + } else { + console.log(`[download-python] All critical packages verified`); + return { success: true, pythonPath: pythonBin, sitePackagesPath: sitePackagesDir }; + } + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + console.log(`[download-python] Existing installation is broken: ${errorMsg}`); fs.rmSync(platformDir, { recursive: true, force: true }); } } @@ -784,6 +807,22 @@ async function downloadPython(targetPlatform, targetArch, options = {}) { // Install packages installPackages(pythonBin, requirementsPath, sitePackagesDir); + // Verify critical packages were installed before creating marker (fixes #416) + // Note: Same list exists in python-env-manager.ts - keep them in sync + // This validation assumes traditional Python packages with __init__.py (not PEP 420 namespace packages) + const criticalPackages = ['claude_agent_sdk', 'dotenv']; + const postInstallMissing = criticalPackages.filter(pkg => { + const pkgPath = path.join(sitePackagesDir, pkg); + const initFile = path.join(pkgPath, '__init__.py'); + return !fs.existsSync(pkgPath) || !fs.existsSync(initFile); + }); + + if (postInstallMissing.length > 0) { + throw new Error(`Package installation failed - missing critical packages: ${postInstallMissing.join(', ')}`); + } + + console.log(`[download-python] All critical packages verified after installation`); + // Create marker file to indicate successful bundling fs.writeFileSync(packagesMarker, JSON.stringify({ bundledAt: new Date().toISOString(), diff --git a/apps/frontend/src/main/python-env-manager.ts b/apps/frontend/src/main/python-env-manager.ts index 778c641a..bd31c72f 100644 --- a/apps/frontend/src/main/python-env-manager.ts +++ b/apps/frontend/src/main/python-env-manager.ts @@ -122,19 +122,36 @@ export class PythonEnvManager extends EventEmitter { return false; } - // Check for the marker file that indicates successful bundling - const markerPath = path.join(sitePackagesPath, '.bundled'); - if (existsSync(markerPath)) { - console.log(`[PythonEnvManager] Found bundle marker, using bundled packages`); - return true; + // Critical packages that must exist for proper functionality + // This fixes GitHub issue #416 where marker exists but packages are missing + // Note: Same list exists in download-python.cjs - keep them in sync + // This validation assumes traditional Python packages with __init__.py (not PEP 420 namespace packages) + const criticalPackages = ['claude_agent_sdk', 'dotenv']; + + // Check each package exists with valid structure (directory + __init__.py) + const missingPackages = criticalPackages.filter((pkg) => { + const pkgPath = path.join(sitePackagesPath, pkg); + const initPath = path.join(pkgPath, '__init__.py'); + // Package is valid if directory and __init__.py both exist + return !existsSync(pkgPath) || !existsSync(initPath); + }); + + // Log missing packages for debugging + for (const pkg of missingPackages) { + console.log( + `[PythonEnvManager] Missing critical package: ${pkg} at ${path.join(sitePackagesPath, pkg)}` + ); } - // Fallback: check if key packages exist - // This handles cases where the marker might be missing but packages are there - const claudeSdkPath = path.join(sitePackagesPath, 'claude_agent_sdk'); - const dotenvPath = path.join(sitePackagesPath, 'dotenv'); - if (existsSync(claudeSdkPath) || existsSync(dotenvPath)) { - console.log(`[PythonEnvManager] Found key packages, using bundled packages`); + // All packages must exist - don't rely solely on marker file + if (missingPackages.length === 0) { + // Also check marker for logging purposes + const markerPath = path.join(sitePackagesPath, '.bundled'); + if (existsSync(markerPath)) { + console.log(`[PythonEnvManager] Found bundle marker and all critical packages`); + } else { + console.log(`[PythonEnvManager] Found critical packages (marker missing)`); + } return true; }