Skip to content

dev: Container agents still orphan the host port on Ctrl+C / process.exit (exit reaper misses the container) #1714

Description

@tejaskash

Summary

The exit-reaper added in #1695 (registerExitCleanup in src/cli/operations/dev/dev-server.ts) fixes the orphaned-child-holding-the-port bug (#1690) for CodeZip dev servers, but not for Container agents. On the process.exit(0) path — i.e. Ctrl+C / kill $PID, which is the exact scenario #1695 targets — a Container dev server keeps running and continues to hold the mapped host port.

Root cause

The reaper reaps the child's process group:

// dev-server.ts
process.kill(-pid, 'SIGKILL');

For ContainerDevServer, getSpawnConfig() returns docker/podman/finch run --rm --name <name> -p <hostPort>:<internalPort> ..., so this.child is the run client, not the server. The actual server runs inside the container, owned by dockerd/containerd in a separate process tree. Group-killing the run-client's process group does not stop the container:

  • --rm cleans up on container stop/exit, not on client death — so it never triggers here.
  • SIGKILL to the run client does not propagate to the container for the client/daemon split (true for Docker, Podman/conmon, and Finch/Lima).

Container teardown lives only in the ContainerDevServer.kill() override (spawn(runtimeBinary, ['stop', containerName])), and the process.exit(0) path bypasses kill() entirely — that bypass is the whole reason the reaper exists.

Repro

# On a Container agent (agent with a Dockerfile / --type container)
agentcore dev --logs --port 9137 &
DEV_PID=$!
# wait for the port to bind, then:
kill $DEV_PID          # or Ctrl+C in an interactive session
sleep 4
lsof -ti :9137
# actual:   container still running, port held (reparented; only freed by manual `docker stop`)
# expected: empty — port freed

The next agentcore dev then fails on the held port until the user manually runs docker stop <name>. (Note: the base prepare() rm -f clears a stale name on next launch, but not a still-running container's live port binding.)

Suggested fix

Make the exit reaper container-aware — on the exit path, stop the container by name (mirroring the kill() override's runtimeBinary stop <containerName>) rather than group-killing the run-client pid. Since process.exit's 'exit' handler must be synchronous, a spawnSync(runtimeBinary, ['stop', containerName]) (or rm -f) is likely the right primitive there.

Context

Related

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions