docs: how to stop a running carry (docker container stop) + the deterministic container name #399

Closed
opened 2026-07-01 05:49:59 +00:00 by coilysiren · 1 comment
Owner

Context

Operator-knowledge gap, surfaced live: a mis-scoped carry on ward#398 had to be killed mid-flight. docker container stop engineer-claude-ward-398 did it cleanly - but nothing documents that procedure or the name it depends on. The existing docs cover adjacent-but-different lifecycle:

  • docs/container-cleanup.md - docker rm of exited containers (the keep-10 sweep, no -f).
  • docs/container-reap.md - teardown/salvage after a run.
  • Neither covers stopping a still-running carry.

The fact to document

A detached carry's container name is deterministic, so an operator who dispatched one can always reconstruct it and stop it:

  • Built by containerRoleName (cmd/ward/container_compute.go:318-323). The carry branch is fmt.Sprintf("%s-%s-%s-%d", role, mode, safeRepoName(repo), issue), and safeRepoName strips the owner. So the shape is engineer-<driver>-<repo>-<issue> - e.g. engineer-claude-ward-398 (not ...coilyco-flight-deck-ward...).
  • Session/surface containers use the other branch, <role>-<driver>-<machine> (no issue number) - worth a one-line contrast so operators do not look for an issue number on those.
  • Stop command: docker container stop <name>.

Where

Add a short "Stopping a running carry" subsection to docs/agent-engineer.md, under "Ref mode: the detached carry" (~line 32). Include the name convention with the container_compute.go ref, the docker container stop command, and the worked example. Cross-link from:

  • docs/agent-surface.md - a surface session dispatches carries and is exactly where an operator may need to stop a mis-scoped one.
  • docs/container-cleanup.md - contrast: cleanup is docker rm of exited containers, stop is for a running one.

Verify before asserting (do not fabricate)

Document what happens after a docker container stop, checked against docs/container-reap.md and the reap wiring - do not guess:

  • Does ward container reap still fire when a carry is stopped externally (SIGTERM/SIGKILL from docker stop), so the run is still swept/salvaged and its issue reopened? Or does an externally-stopped carry skip reap and orphan its work?
  • State the real answer. If reap does NOT run on external stop, say so and note the operator may need a manual cleanup step. The report from the field was that the stop "was pretty smooth," but confirm the reap interaction in code rather than inferring from that.

Scope / mode

headless. Docs-only, documenting existing behavior (the name convention and stop already work; this writes them down). No FEATURES.md entry expected - it is a doc for an existing capability, not a new feature - unless you judge "stop a running carry" warrants an inventory line, in which case add it. Do not claim docker rm/-f for a running carry; docker container stop is the correct verb.

## Context Operator-knowledge gap, surfaced live: a mis-scoped carry on ward#398 had to be killed mid-flight. `docker container stop engineer-claude-ward-398` did it cleanly - but nothing documents that procedure or the name it depends on. The existing docs cover adjacent-but-different lifecycle: - `docs/container-cleanup.md` - `docker rm` of **exited** containers (the keep-10 sweep, no `-f`). - `docs/container-reap.md` - teardown/salvage after a run. - Neither covers **stopping a still-running carry**. ## The fact to document A detached carry's container name is **deterministic**, so an operator who dispatched one can always reconstruct it and stop it: - Built by `containerRoleName` (`cmd/ward/container_compute.go:318-323`). The carry branch is `fmt.Sprintf("%s-%s-%s-%d", role, mode, safeRepoName(repo), issue)`, and `safeRepoName` strips the owner. So the shape is **`engineer-<driver>-<repo>-<issue>`** - e.g. `engineer-claude-ward-398` (not `...coilyco-flight-deck-ward...`). - Session/surface containers use the other branch, `<role>-<driver>-<machine>` (no issue number) - worth a one-line contrast so operators do not look for an issue number on those. - Stop command: `docker container stop <name>`. ## Where Add a short **"Stopping a running carry"** subsection to `docs/agent-engineer.md`, under **"Ref mode: the detached carry"** (~line 32). Include the name convention with the `container_compute.go` ref, the `docker container stop` command, and the worked example. Cross-link from: - `docs/agent-surface.md` - a surface session dispatches carries and is exactly where an operator may need to stop a mis-scoped one. - `docs/container-cleanup.md` - contrast: cleanup is `docker rm` of **exited** containers, stop is for a **running** one. ## Verify before asserting (do not fabricate) Document what happens **after** a `docker container stop`, checked against `docs/container-reap.md` and the reap wiring - do not guess: - Does `ward container reap` still fire when a carry is stopped **externally** (SIGTERM/SIGKILL from `docker stop`), so the run is still swept/salvaged and its issue reopened? Or does an externally-stopped carry skip reap and orphan its work? - State the real answer. If reap does NOT run on external stop, say so and note the operator may need a manual cleanup step. The report from the field was that the stop "was pretty smooth," but confirm the reap interaction in code rather than inferring from that. ## Scope / mode headless. Docs-only, documenting existing behavior (the name convention and stop already work; this writes them down). No FEATURES.md entry expected - it is a doc for an existing capability, not a new feature - unless you judge "stop a running carry" warrants an inventory line, in which case add it. Do not claim `docker rm`/`-f` for a running carry; `docker container stop` is the correct verb.
Author
Owner

WARD-OUTCOME: done - added docs/container-stop.md (deterministic carry name + docker container stop), confirmed in code that an external stop skips the reaper, cross-linked from the engineer/surface/cleanup docs.

The "verify before asserting" ask flipped my first assumption. I would have guessed docker stop still fires the reaper. It does not: the reaper is a bash trap ... EXIT on the PID-1 entrypoint with no SIGTERM/SIGINT handler and no --init shim, so the kernel drops SIGTERM to PID 1 and the follow-up SIGKILL is uncatchable. The EXIT trap never runs, so nothing is salvaged. That squares with the "pretty smooth" field report, smooth precisely because nothing else happened, and for a mis-scoped carry that is the wanted outcome. I still wrote down that unpushed in-progress work is not auto-preserved.

What actually fought back was the docs-size gate, not the content. agent-engineer.md and agent-surface.md were both within ~6 chars of the 4000-char cap, so every new cross-link had to be paid for by trimming duplicate refs and self-evident asides elsewhere. That is why the detail lives in its own doc with short pointers, which is also where the hook nudges you.

Confidence is high on the reap finding (a direct read of entrypoint.sh + the run argv, not an inference), with one caveat: no docker daemon in the feature box, so I could not watch a live stop. One follow-up worth filing: kdlfmt already flags cmd/ward-kdl/ward-kdl-write/ward-kdl.forgejo.write.guardfile.kdl as unformatted on main (pre-existing, unrelated to this issue), so a full pre-commit run --all-files is currently red on that one file.

WARD-OUTCOME: done - added docs/container-stop.md (deterministic carry name + `docker container stop`), confirmed in code that an external stop skips the reaper, cross-linked from the engineer/surface/cleanup docs. The "verify before asserting" ask flipped my first assumption. I would have guessed `docker stop` still fires the reaper. It does not: the reaper is a bash `trap ... EXIT` on the PID-1 entrypoint with no SIGTERM/SIGINT handler and no --init shim, so the kernel drops SIGTERM to PID 1 and the follow-up SIGKILL is uncatchable. The EXIT trap never runs, so nothing is salvaged. That squares with the "pretty smooth" field report, smooth precisely because nothing else happened, and for a mis-scoped carry that is the wanted outcome. I still wrote down that unpushed in-progress work is not auto-preserved. What actually fought back was the docs-size gate, not the content. agent-engineer.md and agent-surface.md were both within ~6 chars of the 4000-char cap, so every new cross-link had to be paid for by trimming duplicate refs and self-evident asides elsewhere. That is why the detail lives in its own doc with short pointers, which is also where the hook nudges you. Confidence is high on the reap finding (a direct read of entrypoint.sh + the run argv, not an inference), with one caveat: no docker daemon in the feature box, so I could not watch a live stop. One follow-up worth filing: kdlfmt already flags cmd/ward-kdl/ward-kdl-write/ward-kdl.forgejo.write.guardfile.kdl as unformatted on main (pre-existing, unrelated to this issue), so a full pre-commit run --all-files is currently red on that one file.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#399
No description provided.