The entrypoint ended in `exec su -s /bin/bash claude -c "exec sleep
infinity"`, so a project container's whole lifetime was a sleep owned by
the unprivileged claude user. Claude Code runs as claude and routinely runs
`pkill sleep`, `pkill -f`, `kill -9 -1` while testing, and any of those
stopped the container: every terminal disconnected and the app kept
showing Running.
Entrypoint: stay resident as root and wait on a root-owned sleep run as
`triple-c-keepalive infinity`, respawned if killed, with a TERM/INT/HUP
trap so `docker stop` returns at once (550 ms, exit 0, vs 2.6 s / 143
before). Unprivileged kills now get EPERM; `sudo pkill sleep` is survived
by the respawn. The drop to claude dated from the initial commit with no
stated purpose; its only reader was the marketplace readiness probe, which
now accepts either generation of entrypoint.
App: container_watch.rs holds one Docker event subscription filtered
daemon-side to container `die`. A die naming a Running project's container
is confirmed with an inspect, then ProjectsStore::mark_exited flips it to
Stopped as a compare-and-set that skips any project a command holds the
project lock on. Each (re)subscription re-checks Running projects and asks
for events since just before it. The frontend applies the new
`project-status-changed` event and toasts why the sessions died.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>