Skip to content

[Bug bounty] POSIX restart can report success after both companions exit #154

Description

@jamilahmadzai

Bounty eligibility

  • I have signed up at monk.io with this GitHub account
  • I have used the product (installed the plugin and ran it, rather than only reading the code)
  • I have starred this repo

Stage

connect

Coding agent

OpenAI Codex

Coding agent version

Codex desktop; Monk plugin v0.1.47 (271cf17)

Repro steps

The POSIX launcher stops a recorded background companion and starts its replacement without waiting for the old process to exit:

if [ -f "$pid_file" ]; then
  old_pid="$(cat "$pid_file" 2>/dev/null || true)"
  if [ -n "$old_pid" ]; then
    kill "$old_pid" >/dev/null 2>&1 || true
  fi
fi
# replacement starts immediately

That creates a deterministic restart race when the old companion needs time to release its listener:

  1. Start an isolated old companion fixture whose health endpoint remains available for two seconds after SIGTERM.
  2. Put its PID in the launcher's normal monk-agent.pid path.
  3. Make the managed-agent ensure step install a different executable so agent_updated=1 and the healthy fast path is bypassed.
  4. Make the replacement fixture match monk-agent's healthy-port-conflict behavior: if the old endpoint is still present, defer to it and exit 0.
  5. Run the unmodified v0.1.47 scripts/start-monk-agent.sh through its Linux background-process path, with health and process behavior isolated behind local filesystem stubs.
  6. After the launcher returns, wait for the old process's two-second shutdown to complete and check whether the replacement is alive.

Observed on repeated runs:

launcher_exit=0
old_companion_alive=no
replacement_companion_alive=no
replacement_start_attempts=1
launcher returned success before the replacement companion was running

The replacement starts while the old companion still owns the simulated endpoint and exits cleanly. The launcher's first readiness check then reaches the old companion and returns success. Once the delayed old shutdown finishes, no companion remains and the PID file points to the exited replacement.

This is distinct from existing reports:

Repository searches for restart handoff, replacement companion, old agent port, port conflict restart, start_with_background_process wait, and binary update launcher found no same-scope issue or PR.

Expected behavior

A managed POSIX restart should complete the old companion's shutdown before launching its replacement. Returning success must mean the replacement process—not the process being stopped—has passed readiness and remains running.

Actual behavior

The replacement can start before the old companion releases the port, defer to the old healthy process, and exit. The launcher then mistakes the stopping old process for successful replacement readiness and exits 0. Shortly afterward both processes are gone and Monk MCP is unavailable despite the successful SessionStart hook.

Severity (your guess)

major

OS

macOS 26.5.2, Apple Silicon, using the shipped POSIX launcher's Linux background-process path with deterministic process/health stubs

monkd version

Not involved; failure occurs in local companion restart handoff before any monkd operation

Target cloud

None; local companion only

Integration (if relevant)

Monk plugin POSIX SessionStart launcher for Codex and Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions