diff --git a/apps/api/src/routes/features.ts b/apps/api/src/routes/features.ts index cc718062..235dfd1a 100644 --- a/apps/api/src/routes/features.ts +++ b/apps/api/src/routes/features.ts @@ -225,13 +225,24 @@ export async function registerFeatureRoutes(app: FastifyInstance): Promise } } if (!errorMsg) { - const meaningful = lastStderrLines.filter( - (l) => !l.startsWith("{") && !l.includes("pthread_setaffinity_np"), - ); - errorMsg = - meaningful.join("\n") || - stdoutBuffer.trim() || - `Install failed with exit code ${code}`; + if (code === 137) { + errorMsg = + "Installation was killed due to insufficient memory. " + + "Try increasing the container's memory limit (e.g. mem_limit: 6g in docker-compose.yml) and retry."; + } else { + const meaningful = lastStderrLines.filter( + (l) => + !l.startsWith("{") && + !l.includes("pthread_setaffinity_np") && + !l.includes("\x1b[") && + !l.includes("━") && + !/^\s*\d+%\|/.test(l), + ); + errorMsg = + meaningful.join("\n") || + stdoutBuffer.trim() || + `Install failed with exit code ${code}`; + } } setInstallProgress(bundleId, null, errorMsg); updateSingleFileProgress({ jobId, phase: "failed", percent: 0, error: errorMsg }); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2717eec3..659972ed 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -52,8 +52,8 @@ services: # - COOKIE_SECRET= restart: unless-stopped # --- Security hardening --- - mem_limit: 4g - memswap_limit: 4g + mem_limit: 6g + memswap_limit: 6g cpus: 4 pids_limit: 512 cap_drop: diff --git a/packages/ai/python/install_feature.py b/packages/ai/python/install_feature.py index ef228049..d5d399c7 100644 --- a/packages/ai/python/install_feature.py +++ b/packages/ai/python/install_feature.py @@ -125,7 +125,7 @@ def check_disk_space(path: str, min_bytes: int = 100 * 1024 * 1024) -> None: def pip_install(package: str, extra_flags: list[str] | None = None) -> None: """Run pip install for a single package spec. Raises on failure.""" - cmd = [sys.executable, "-m", "pip", "install"] + cmd = [sys.executable, "-m", "pip", "install", "--no-cache-dir"] if extra_flags: cmd.extend(extra_flags) @@ -471,7 +471,7 @@ def download_models(models: list[dict], models_dir: str) -> list[str]: except Exception as e: return (model_id, e) - with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool: + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool: futures = { pool.submit(_download, i, m): i for i, m in enumerate(models)