Skip to content

[Refactoring] Extract retry and diagnostic phases from startContainers (144 lines) in src/container-lifecycle.ts #5751

Description

@github-actions

Refactoring Opportunity

Summary

  • File: src/container-lifecycle.ts
  • Current size: 291 lines
  • Function of concern: startContainers spans lines 43–186 (144 lines)
  • Responsibilities identified: 4 distinct concerns in one function body
  • Score: 7 (functions >80 lines +2, security-critical container startup +2, clearly >2 responsibilities +3)
  • Prior issue: [Refactoring] Split src/container-lifecycle.ts into focused modules #4536 ("Split src/container-lifecycle.ts into focused modules") was resolved — that split created the current file. The remaining startContainers function still has four distinct concerns.

Evidence

startContainers (lines 43–186) orchestrates the entire container bring-up path. It currently handles four distinct concerns:

1. Orphaned container pre-cleanup (lines 47–57)

await execa('docker', ['rm', '-f', SQUID_CONTAINER_NAME, AGENT_CONTAINER_NAME, ...], { reject: false });

This defensive cleanup fires before every startup and is unrelated to the actual bring-up logic.

2. Topology-mode two-phase startup (lines 80–102)
When onNetworkReady is provided, the function performs an initial squid-proxy-only start, awaits a caller-supplied callback, then falls through to the normal full bring-up. This conditional path spans ~23 lines inside the function body.

3. Core Docker Compose bring-up (lines 65–78, 104–106)
The runDockerComposeUp inner closure and its single-path invocation is the function's primary intent (~20 lines).

4. Startup error handling, retry, and diagnostics (lines 107–185, ~78 lines)
This is the most complex part: detects which container failed (api-proxy, squid, cli-proxy), conditionally retries once, logs container logs to stderr, constructs specific error messages per container, and delegates to handleHealthcheckError. Spanning 78 lines, it accounts for more than half the function.

Proposed Split

Extract the retry/diagnostic block into a focused helper:

  • attemptContainerStartup(workDir, composeArgs, skipPull, onNetworkReady)
    Handles the topology-mode split start plus the single runDockerComposeUp call (~40 lines). Returns on success; throws on failure.

  • handleStartupFailure(error, workDir, proxyLogsDir, allowedDomains)
    Encapsulates the retry-once logic and all per-container diagnostic paths (~80 lines). Called only in the catch branch.

  • startContainers reduced to ~40 lines
    Pre-cleanup → attemptContainerStartup() → on failure → handleStartupFailure().

Additionally, runAgentCommand (lines 191–268, 88 lines) has the same pattern: a timeout-vs-wait race, log streaming, blocked-domain checking, and exit-code propagation mixed in one function. That function is a separate but related candidate.

Affected Callers

src/docker-manager.ts                  — imports startContainers, runAgentCommand, fastKillAgentContainer
src/container-lifecycle-branches.test.ts
src/container-start.test.ts
src/container-lifecycle-retry-timeout.test.ts
src/run-agent-command.test.ts

The helper functions would be module-private (no callers to update for the extraction). Only startContainers's signature is preserved unchanged.

Effort Estimate

Medium — the retry logic has several branches that need careful extraction to preserve test coverage.

Benefits

  • startContainers drops from 144 lines to ~40 lines
  • Retry/diagnostic logic independently testable without mocking the full compose bring-up
  • Easier to locate the security boundary: orphaned-container cleanup, topology mode, normal startup, and failure diagnostics are each in named, navigable functions
  • Reduces risk of introducing regressions when modifying one phase (e.g., adding a new per-container failure path)

Detected by Refactoring Scanner workflow. Run date: 2026-07-01

Generated by Refactoring Opportunity Scanner · 173.9 AIC · ⊞ 7K ·

  • expires on Aug 30, 2026, 4:01 PM UTC

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions