Skip to content

Codex-generated test harness leaked unbounded Docker/Xcode resources without post-run verification #33707

Open

Description

@runminglu

What version of Codex was running?

The harness was generated during a codex-tui session on the 0.144.0 line. The current installed binary is codex-cli 0.144.5.

Which model was being used?

gpt-5.6-sol, reasoning effort ultra.

What platform was the computer using?

macOS 26.4 (25E246), arm64, with Docker Desktop and Xcode/iOS Simulator.

What issue occurred?

Codex designed and committed an iOS E2E/fault-test harness that created unbounded external resources and then repeatedly ran it without verifying resource cleanup.

The generated harness used:

  • a unique Docker Compose project name for every fault scenario/run
  • docker compose down --remove-orphans, but no removal of project-local images
  • a new DerivedData path and .xcresult bundle for each run
  • no retention policy for DerivedData or result bundles
  • simulators that could be booted for a run but were not shut down afterward
  • shared/global BuildKit cache with no run ownership or bound
  • traps for the immediate shell, but no stale-run reaper for crashes/SIGKILL
  • no post-run assertion that Docker/Xcode/simulator resource usage returned to baseline

This was not primarily an interrupt/cancellation failure. The resources accumulated after successful runs because the generated harness itself had incomplete lifecycle semantics.

After many E2E runs:

  • the host had about 191 MB free
  • Docker Desktop's physical Docker.raw usage was about 227 GB
  • Docker Desktop crashed with no space left on device
  • Xcode showed many retained test/run entries
  • DerivedData, result bundles, project-local images, BuildKit cache, and simulator state had accumulated

After safely pruning only unused Docker data and repairing the harness (without removing Docker volumes), the host recovered more than 200 GB and Docker physical usage dropped to roughly 51 GB.

Reproduction pattern

This is a model/agent behavior reproduction rather than a deterministic CLI command:

  1. Ask Codex to build a comprehensive iOS E2E matrix with Docker Compose-based transport-fault services and Xcode result bundles.
  2. Let Codex design the runner and execute it repeatedly across multiple scenarios/devices.
  3. Inspect the generated lifecycle code.
  4. Run the suite repeatedly and compare:
    • Docker images, containers, builders, and build cache
    • DerivedData and .xcresult directories
    • booted simulators
    • free host disk
  5. Observe that immediate containers are stopped, but run-owned images, cache, Xcode artifacts, and simulator state remain and grow without a bound.

Representative generated patterns were equivalent to:

compose_project="...-${run_id}-${label}-$$"
derived_data=".../${run_id}/${label}"
result_bundle=".../${run_id}/${label}/result.xcresult"

docker compose --project-name "$compose_project" up -d --build
# ...
docker compose --project-name "$compose_project" down --remove-orphans

Stopping the Compose project did not remove the uniquely named project image, and neither artifact path had retention.

What was expected?

When Codex generates and executes test infrastructure, it should treat every external resource it creates as owned state and verify a bounded lifecycle:

  • cleanup on success, failure, and catchable signals
  • explicit ownership markers
  • stale-run recovery for crashes or SIGKILL
  • bounded artifact retention
  • cleanup of run-owned images/builders/cache without touching user volumes
  • simulator shutdown when the runner booted it
  • a post-run resource-delta check before reporting the task complete

The agent should not need to globally prune Docker or delete arbitrary Xcode data. It should generate scoped cleanup for resources it owns.

Why this appears to be an agent-quality issue

The CLI executed the generated scripts as written. It cannot safely infer that an arbitrary Docker image, simulator, or Xcode artifact is disposable. The defect was that Codex generated infrastructure with incomplete ownership and cleanup semantics, repeatedly ran it, and declared the work complete without checking the machine-level resource delta.

This therefore appears to be a model/agent planning and verification gap rather than primarily a shell process-tree bug.

Remediation applied locally

The repaired harness now includes:

  • active-run markers and stale-run reaping
  • run-owned DerivedData cleanup
  • bounded result retention
  • simulator shutdown only when the runner booted it
  • Compose cleanup with run-owned local image removal
  • a run-owned Buildx builder with a pinned BuildKit version and memory limit
  • removal of the run-owned builder/state after the run
  • explicit protection of Docker volumes

A useful regression/evaluation would ask the model to generate a multi-run Docker/Xcode test harness, run it several times, and assert that all created external resources are either removed or retained under a documented bound.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    model-behaviorIssues related to behaviors exhibited by the model
    on Jul 16, 2026
  2. runminglu commented on Jul 16, 2026

    @runminglu
    Author

    Successful mitigation and recovery approach

    The prompt that successfully initiated recovery was:

    Investigate and fix the resource leaks in Xcode and Docker. Every time many iOS E2E tests run, memory and disk are nearly full; Xcode shows many unused projects; Docker usage is high and temporary containers accumulate.

    Codex was able to diagnose and repair the problem when explicitly asked to investigate the machine-level resource leak, rather than only fix a failing test. The effective workflow was:

    1. Measure before deleting anything

    Codex inspected:

    • host free space and the largest relevant directories
    • Docker containers, images, Compose project labels, BuildKit builders, and build cache
    • Docker Desktop's physical Docker.raw usage
    • Xcode DerivedData and .xcresult directories
    • booted iOS simulators
    • the test scripts that created each resource

    The important step was correlating run IDs, Compose project names, artifact paths, and simulator state back to the generated harness. This separated runner-owned resources from unrelated user resources.

    2. Recover capacity with ownership-scoped cleanup

    The recovery removed only unused or clearly runner-owned data:

    • unused Docker images, stopped temporary containers, and build cache
    • obsolete runner-created DerivedData and result bundles
    • stale fault-test Compose projects/builders
    • simulators that were left booted by the runner

    Docker volumes were explicitly protected. The recovery did not use docker compose down -v or docker volume rm, because the application's SQLite database and uploads live in a Docker volume.

    This restored more than 200 GB of host capacity and reduced Docker's physical disk usage from roughly 227 GB to roughly 51 GB.

    3. Encode lifecycle ownership into the harness

    The repair added:

    • active-run marker files
    • a stale-run reaper for crashes and SIGKILL cases
    • run-owned DerivedData cleanup
    • bounded result retention (keep the newest three runs)
    • simulator state tracking and shutdown only when the runner booted it
    • Compose teardown with --rmi local for run-owned project images
    • a run-owned Buildx builder instead of unbounded shared builder state
    • a pinned BuildKit image, a 4 GB memory limit, and builder removal after the run
    • normal and signal cleanup paths
    • explicit Docker-volume protection

    The key invariant was: every external resource created by the runner must either be removed at completion or retained under a documented bound.

    4. Verify cleanup, including interruption paths

    Codex verified the fix with:

    • shell syntax checks and dry runs
    • Docker Compose configuration validation
    • retention-helper tests
    • a real interrupted transport-fault run
    • Buildx builder create/use/remove lifecycle checks
    • post-run checks for remaining fault containers, project images, builders, booted simulators, DerivedData, and result bundles
    • an application health check after cleanup

    The task was considered complete only after a run returned the machine to the expected bounded baseline.

    Suggested Codex behavior/evaluation

    For tasks that generate Docker, simulator, browser, database, VM, or test artifacts, Codex should automatically include a resource-lifecycle review:

    1. inventory the external resources the proposed code creates
    2. define ownership markers for each resource
    3. implement success, failure, signal, and stale-run cleanup
    4. establish retention bounds for intentionally preserved artifacts
    5. protect unrelated/user-owned state
    6. run the workflow repeatedly and compare pre/post resource deltas
    7. refuse to report completion while owned resources continue to grow without a bound

    This incident shows that Codex can successfully repair this class of problem once the prompt explicitly frames it as a machine-level resource leak. The improvement would be to make that lifecycle analysis part of the default design and verification behavior when Codex creates the harness in the first place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingmodel-behaviorIssues related to behaviors exhibited by the model

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions