Skip to content

[Bug bounty] Custom agent path switch returns success with the old companion still running #270

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 CLI 0.146.0-alpha.9.2; Monk plugin v0.1.54 (2a4f9cf)

Repro steps

The v0.1.54 background launcher correctly notices when MONK_AGENT_PATH changes from custom executable A to B, but its stop step validates the recorded PID against B instead of the executable that actually owns the PID (A).

I reproduced the complete false-success handoff with a deterministic, network-free launcher test:

  1. Start a long-running custom executable A and write its PID plus agent_path=A to the launcher's normal PID/state files.
  2. Make the normal protected-resource probe return the expected healthy Monk MCP resource for A.
  3. Set MONK_AGENT_PATH=B. B models the companion's healthy-port-conflict behavior: it briefly waits for A to release the endpoint, then exits 0 when A is still alive.
  4. Run the unmodified scripts/start-monk-agent.sh through its Linux/background-process path.
  5. After the launcher returns, inspect both processes and the launcher's PID/state files.

Observed on v0.1.54:

launcher_exit=0
old_companion_A_alive=yes
replacement_B_alive=no
pid_file_points_to=B
state_file_agent_path=B

The focused regression reports:

launcher returned success against the old custom companion while the replacement exited

The root cause is in start_with_background_process():

old_pid="$(cat "$pid_file" ...)"
if pid_matches_executable "$old_pid" "$agent_path" && kill -0 "$old_pid"; then
  kill "$old_pid"
fi

At this point $agent_path is B, while $old_pid belongs to A. The ownership check therefore rejects the correct old process and never signals it. The launcher starts B, B defers to the healthy A and exits, then readiness succeeds against A before liveness can reject B.

The native Windows path has the same ownership mismatch: Stop-ManagedAgent compares the old process path with the newly selected $AgentPath, even though the state file records the prior path.

This is distinct from #10/#36. Those reports fixed repeated restarts when the same custom path is reused. #10 explicitly expected a one-time restart when switching to a different custom executable, but the shipped handoff cannot complete that switch. It is also distinct from #154/#180, which cover restart races without changing executable paths.

Fresh issue and PR searches for MONK_AGENT_PATH, custom agent/companion, path switch/drift, replacement, handoff, old PID, and restart found no report or implementation for this A-to-B ownership mismatch.

Expected behavior

When MONK_AGENT_PATH changes from A to B, the launcher should authenticate the recorded PID against the previously recorded A path, stop A, start B, and return success only after B owns the healthy endpoint. The PID and state files should both describe the running B process.

Actual behavior

The launcher authenticates A's PID against B, refuses to stop A, starts B into A's occupied endpoint, and can return success against A after B exits. It then leaves PID/state metadata pointing to the dead replacement even though the old custom companion is still serving.

Severity (your guess)

major

The launcher reports a successful configuration switch that did not occur, and later lifecycle/uninstall operations lose ownership of the process still serving the MCP endpoint. Manually stopping A before switching paths is a workaround.

OS

macOS 26.6 arm64 host using the repository's deterministic fixture to execute the exact Linux/background-process branch; equivalent ownership mismatch confirmed in the shipped Windows PowerShell path

monkd version

Not involved; the failure is in the local companion handoff before any monkd operation

Target cloud

None

Integration (if relevant)

Monk plugin custom-agent launcher used by OpenAI Codex and Google Antigravity on Linux; equivalent native Windows launcher logic

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