diff --git a/src/container-cleanup.ts b/src/container-cleanup.ts index 117117c89..b97a77728 100644 --- a/src/container-cleanup.ts +++ b/src/container-cleanup.ts @@ -172,6 +172,22 @@ export async function collectDiagnosticLogs(workDir: string): Promise { logger.info(`Diagnostic logs collected at: ${diagnosticsDir}`); } +/** + * Runs `docker compose down -v -t 1` with the standard AWF options. + */ +export async function runComposeDown( + workDir: string, + options: { reject?: boolean } = {}, +): Promise { + await execa('docker', ['compose', 'down', '-v', '-t', '1'], { + cwd: workDir, + stdout: process.stderr, + stderr: 'inherit', + env: getLocalDockerEnv(), + reject: options.reject ?? true, + }); +} + /** * Stops and removes Docker Compose services */ @@ -184,12 +200,7 @@ export async function stopContainers(workDir: string, keepContainers: boolean): logger.info('Stopping containers...'); try { - await execa('docker', ['compose', 'down', '-v', '-t', '1'], { - cwd: workDir, - stdout: process.stderr, - stderr: 'inherit', - env: getLocalDockerEnv(), - }); + await runComposeDown(workDir); logger.success('Containers stopped successfully'); } catch (error) { logger.error('Failed to stop containers:', error); diff --git a/src/container-lifecycle.ts b/src/container-lifecycle.ts index 78ddb856e..f30ffca84 100644 --- a/src/container-lifecycle.ts +++ b/src/container-lifecycle.ts @@ -4,6 +4,7 @@ import execa from 'execa'; import { BlockedTarget } from './types'; import { logger } from './logger'; import { parseDomainWithProtocol, isWildcardPattern, wildcardToRegex } from './domain-patterns'; +import { runComposeDown } from './container-cleanup'; import { AGENT_CONTAINER_NAME, SQUID_CONTAINER_NAME, @@ -218,13 +219,7 @@ export async function startContainers(workDir: string, allowedDomains: string[], // Tear down before retry so Docker Compose starts fresh try { - await execa('docker', ['compose', 'down', '-v', '-t', '1'], { - cwd: workDir, - stdout: process.stderr, - stderr: 'inherit', - env: getLocalDockerEnv(), - reject: false, - }); + await runComposeDown(workDir, { reject: false }); } catch (cleanupError) { // Best-effort cleanup — proceed with retry regardless logger.debug('Cleanup before retry failed (proceeding anyway):', cleanupError); diff --git a/src/docker-manager-lifecycle.test.ts b/src/docker-manager-lifecycle.test.ts index e65d3ad98..1cbbb2dc0 100644 --- a/src/docker-manager-lifecycle.test.ts +++ b/src/docker-manager-lifecycle.test.ts @@ -137,6 +137,11 @@ describe('docker-manager lifecycle', () => { call[0] === 'docker' && Array.isArray(call[1]) && call[1].includes('up') ); expect(upCalls).toHaveLength(2); + expect(mockExecaFn).toHaveBeenCalledWith( + 'docker', + ['compose', 'down', '-v', '-t', '1'], + expect.objectContaining({ cwd: testDir, stdout: process.stderr, stderr: 'inherit', reject: false }) + ); }); it('should retry once when awf-api-proxy exits during startup', async () => {