Skip to content

Docker image must own daemon lifecycle and prevent netclaw init from spawning orphaned netclawd #1279

Description

@Aaronontheweb

Problem

The official NetClaw Docker image can enter a split-brain daemon lifecycle after netclaw init is run inside the container.

This is not a deployment-specific issue. The image itself currently has two daemon lifecycle owners:

  • docker/entrypoint.sh starts and supervises netclawd.
  • netclaw init uses DaemonManager.Start(), which can spawn a detached netclawd.

Inside a container, the CLI should never spawn a detached daemon. The container entrypoint must be the only daemon supervisor.

Observed Behavior

After running netclaw init inside the container, the image can end up with a daemon reported as running while the entrypoint logs repeatedly show lock contention:

error: Another netclawd instance is already running (lock file held). Exiting.
[entrypoint] netclawd exited (code=1) after 0s, restarting in 60s...

A process listing can show the live netclawd process associated with an interactive exec session TTY and later orphaned to PID 1, rather than being the entrypoint-supervised child.

Root Cause

HealthCheckStepViewModel stops the daemon for config update, writes config, then starts it again:

var stopResult = await _daemonManager.StopAsync("config-update");
...
var result = _daemonManager.Start();

DaemonManager.Start() starts a detached process. That is valid for host installs, but invalid inside the official Docker image because PID 1 is already the daemon supervisor.

Expected Behavior

The Docker image must be self-contained and container-native:

  • PID 1 / entrypoint.sh is the only process allowed to start netclawd.
  • netclaw init inside the official image must not call detached daemon start.
  • Config updates should restart through the image-owned lifecycle.
  • The image should never require Kubernetes-specific workarounds, external rollout procedures, or deployment-specific environment variables to recover.

Proposed Fix

Make the Docker image declare and enforce a container-supervised mode.

For example, in docker/Dockerfile, set an image-owned marker:

ENV NETCLAW_CONTAINER_SUPERVISOR=entrypoint

In DaemonManager.Start() or the wizard layer, detect this mode and refuse detached starts:

If NETCLAW_CONTAINER_SUPERVISOR=entrypoint:
  do not spawn netclawd
  return success if daemon is already healthy
  otherwise wait for PID 1 entrypoint to start/restart it

Update HealthCheckStepViewModel so container mode uses the container lifecycle:

write config
request daemon reload/restart through the running daemon if available
signal or notify PID 1 entrypoint if daemon is not currently running
poll readiness
never call detached DaemonManager.Start()

The daemon already has an internal restart primitive:

IDaemonRestartCoordinator.RequestConfigRestartAsync(...)

That should be reused or exposed through a local lifecycle endpoint for config-update restarts instead of stopping the process and spawning a detached replacement.

Also harden docker/entrypoint.sh defensively:

If netclawd exits because the lock is held:
  inspect netclaw.pid
  if a live netclawd owns the lock, log a clear split-brain diagnostic
  reclaim ownership by terminating the rogue daemon or adopt/monitor it safely
  never loop forever with only lock-file errors

The entrypoint should also reset crash-loop backoff after config is written so first-run setup does not wait up to 60 seconds for a successful restart.

Acceptance Criteria

  • Running netclaw init inside the official Docker image never creates a detached/orphaned daemon.
  • After init completes, exactly one netclawd process exists.
  • The live daemon is owned by the container lifecycle, not an interactive exec session.
  • docker logs does not show repeated lock-file restart failures.
  • docker stop sends termination to the actual daemon.
  • No Kubernetes-specific workaround is required.
  • No deployment-specific environment variable is required.
  • The fix works for plain Docker and Kubernetes equally.

Suggested Tests

  • Container smoke test starts the official image with an empty /home/netclaw/.netclaw, runs the init/config-write path from docker exec, and asserts only one netclawd exists.
  • Test asserts the daemon process is not associated with the exec session TTY.
  • Test asserts entrypoint logs do not contain Another netclawd instance is already running.
  • Unit test verifies container-supervised mode prevents DaemonManager.Start() from spawning a detached process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    configConfiguration issues, netclaw doctor, schema validation.enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions