From e3b2ffce3619d30dcdff145fdedf67cc2521c316 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Sat, 4 Jul 2026 16:04:56 +0300 Subject: [PATCH] Build agent images asynchronously instead of blocking the host Replace the execSync docker build in buildAgentGroupImage with an awaited promisified exec so the single-threaded host stays responsive during the image build (up to 15 minutes) that a package-install approval or `ncl groups restart --rebuild` triggers. Timeout, buffered stdio, and non-zero-exit error propagation are preserved; both callers already await. Co-Authored-By: Claude Opus 4.8 --- src/container-runner.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/container-runner.ts b/src/container-runner.ts index c5abbdba3..99954e510 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -3,9 +3,10 @@ * Spawns agent containers with session folder + agent group folder mounts. * The container runs the v2 agent-runner which polls the session DB. */ -import { ChildProcess, execSync, spawn } from 'child_process'; +import { ChildProcess, exec, spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; +import { promisify } from 'util'; import { OneCLI } from '@onecli-sh/sdk'; @@ -510,6 +511,8 @@ async function buildContainerArgs( return args; } +const execAsync = promisify(exec); + /** Build a per-agent-group Docker image with custom packages. */ export async function buildAgentGroupImage(agentGroupId: string): Promise { const agentGroup = getAgentGroup(agentGroupId); @@ -545,9 +548,12 @@ export async function buildAgentGroupImage(agentGroupId: string): Promise const tmpDockerfile = path.join(DATA_DIR, `Dockerfile.${agentGroupId}`); fs.writeFileSync(tmpDockerfile, dockerfile); try { - execSync(`${CONTAINER_RUNTIME_BIN} build -t ${imageTag} -f ${tmpDockerfile} .`, { + // Awaited async exec so the single-threaded host stays responsive during + // the build (can take minutes) instead of blocking on execSync. exec buffers + // stdout/stderr (matching the old stdio: 'pipe') and rejects on a non-zero + // exit, so error propagation is unchanged. + await execAsync(`${CONTAINER_RUNTIME_BIN} build -t ${imageTag} -f ${tmpDockerfile} .`, { cwd: DATA_DIR, - stdio: 'pipe', timeout: 900_000, }); } finally {