Duplicate Code Opportunity
Summary
- Pattern: Two pairs of nearly-identical startup-failure detection functions differ only in the container name constant they reference:
isApiProxyStartupFailureError / isSquidStartupFailureError (4 lines each) and didApiProxyFailStartup / didSquidFailStartup (~24 lines each)
- Locations:
src/container-lifecycle.ts lines 92–131 (api-proxy pair) and 136–170 (squid pair)
- Impact: ~50 lines of near-identical logic; any bug fix or change to startup-failure detection must be applied in two places
Evidence
isApiProxyStartupFailureError (lines 92–98) vs isSquidStartupFailureError (lines 136–142) — identical logic, only container name differs:
// api-proxy version
function isApiProxyStartupFailureError(errorMsg: string): boolean {
if (!errorMsg.includes(API_PROXY_CONTAINER_NAME)) {
return false;
}
return errorMsg.includes('is unhealthy') || errorMsg.includes('exited (1)');
}
// squid version — identical except constant
function isSquidStartupFailureError(errorMsg: string): boolean {
if (!errorMsg.includes(SQUID_CONTAINER_NAME)) {
return false;
}
return errorMsg.includes('is unhealthy') || errorMsg.includes('exited (1)');
}
didApiProxyFailStartup (lines 103–130) vs didSquidFailStartup (lines 147–170) — identical logic, only container name and log message differ:
// api-proxy version
async function didApiProxyFailStartup(errorMsg: string): Promise<boolean> {
if (isApiProxyStartupFailureError(errorMsg)) { return true; }
try {
const result = await execa(
'docker',
['inspect', API_PROXY_CONTAINER_NAME, '--format', '{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{end}}'],
{ reject: false, env: getLocalDockerEnv() }
);
if (result.exitCode !== 0) return false;
const [containerStatus = '', healthStatus = ''] = result.stdout.trim().split('|');
return containerStatus === 'exited' || healthStatus === 'unhealthy';
} catch (error) {
logger.debug(`Could not inspect ${API_PROXY_CONTAINER_NAME} after startup failure:`, error);
return false;
}
}
// squid version — identical except constant name
async function didSquidFailStartup(errorMsg: string): Promise<boolean> {
if (isSquidStartupFailureError(errorMsg)) { return true; }
try {
const result = await execa(
'docker',
['inspect', SQUID_CONTAINER_NAME, '--format', '{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{end}}'],
{ reject: false, env: getLocalDockerEnv() }
);
if (result.exitCode !== 0) return false;
const [containerStatus = '', healthStatus = ''] = result.stdout.trim().split('|');
return containerStatus === 'exited' || healthStatus === 'unhealthy';
} catch (error) {
logger.debug(`Could not inspect ${SQUID_CONTAINER_NAME} after startup failure:`, error);
return false;
}
}
Suggested Refactoring
Collapse the four functions into two generic helpers parameterised on containerName:
function isContainerStartupFailureError(errorMsg: string, containerName: string): boolean {
if (!errorMsg.includes(containerName)) return false;
return errorMsg.includes('is unhealthy') || errorMsg.includes('exited (1)');
}
async function didContainerFailStartup(errorMsg: string, containerName: string): Promise<boolean> {
if (isContainerStartupFailureError(errorMsg, containerName)) return true;
try {
const result = await execa(
'docker',
['inspect', containerName, '--format', '{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{end}}'],
{ reject: false, env: getLocalDockerEnv() }
);
if (result.exitCode !== 0) return false;
const [containerStatus = '', healthStatus = ''] = result.stdout.trim().split('|');
return containerStatus === 'exited' || healthStatus === 'unhealthy';
} catch (error) {
logger.debug(`Could not inspect ${containerName} after startup failure:`, error);
return false;
}
}
Call sites become:
if (await didContainerFailStartup(errorMsg, API_PROXY_CONTAINER_NAME)) ...
if (await didContainerFailStartup(errorMsg, SQUID_CONTAINER_NAME)) ...
Affected Files
src/container-lifecycle.ts — lines 92–170
Effort Estimate
Low
Detected by Duplicate Code Detector workflow. Run date: 2026-06-04
Generated by Duplicate Code Detector · sonnet46 2.2M · ◷
Duplicate Code Opportunity
Summary
isApiProxyStartupFailureError/isSquidStartupFailureError(4 lines each) anddidApiProxyFailStartup/didSquidFailStartup(~24 lines each)src/container-lifecycle.tslines 92–131 (api-proxy pair) and 136–170 (squid pair)Evidence
isApiProxyStartupFailureError(lines 92–98) vsisSquidStartupFailureError(lines 136–142) — identical logic, only container name differs:didApiProxyFailStartup(lines 103–130) vsdidSquidFailStartup(lines 147–170) — identical logic, only container name and log message differ:Suggested Refactoring
Collapse the four functions into two generic helpers parameterised on
containerName:Call sites become:
Affected Files
src/container-lifecycle.ts— lines 92–170Effort Estimate
Low
Detected by Duplicate Code Detector workflow. Run date: 2026-06-04