fix(mcp): block manual daemon killing as workflow recovery (Closes #630)
A session that ran `pkill -f mcp_server.py`, waited for the IDE to respawn the daemons, and then closed an issue left no trace distinguishing that closure from one performed over a sanctioned runtime. Detection existed only as advisory strings (`native_mcp_preference.classify_command_path`, `review_workflow_boundary`) and never failed closed on the mutations that followed. Adds `runtime_recovery_guard.py`, mirroring the #671 stable-branch contamination model so the two cannot drift apart: classification of kill/pkill/killall commands and known-pid kills, a durable `runtime_recovery_contamination` marker, a fail-closed pre-flight gate over the shared review/merge/close/completion task set, and reconciler-only clearing. `comment_issue` and `lock_issue` stay allowed so a contaminated worker can still post its audit comment and hand off. The marker is recovery-critical, so contamination cannot expire into cleanliness with the session-state TTL. Read-only inspection (`ps aux | grep mcp_server`), sanctioned reconnects, and process management unrelated to the daemons are never flagged; a bare `kill` of an unknown pid is reported as ambiguous rather than contaminating, so ordinary subprocess work is not false-blocked. A pattern broad enough to sweep unrelated namespaces (`pkill -f python`) is contamination even when it never names MCP. Operator-authorized host maintenance stays permitted, but the authorization is read from GITEA_OPERATOR_DAEMON_MAINTENANCE_AUTHORIZATION in the process environment only and never from a tool argument, so a session cannot authorize itself. Final-report rules reject clean-session claims and require the contaminated recovery to be surfaced. Two tools are registered (inventory 112 to 114) and the sanctioned-versus-forbidden contrast is documented in the namespace recovery doc and the workflow skill. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -168,6 +168,42 @@ Tooling: call `gitea_record_stable_branch_push_attempt` to classify/record a
|
||||
proposed push before running it; `gitea_audit_stable_branch_contamination` to
|
||||
inspect or (reconciler-only) clear the marker.
|
||||
|
||||
## Runtime Recovery Protection (#630)
|
||||
|
||||
MCP connectivity is recovered through **sanctioned reconnect/restart only**:
|
||||
host auto-reconnect, an explicit client reconnect, an IDE/client relaunch, or an
|
||||
operator-owned restart. Worker sessions must never kill the daemons their own
|
||||
proof depends on.
|
||||
|
||||
**Forbidden for author/reviewer/merger sessions:**
|
||||
|
||||
- `pkill -f mcp_server.py`, `pkill -f gitea_mcp_server`, broad `pkill -f mcp`.
|
||||
- `killall` of a daemon, or `kill <pid>` of an MCP daemon pid.
|
||||
- Any pattern broad enough to sweep unrelated namespaces (`pkill -f python`),
|
||||
even when it never names MCP.
|
||||
|
||||
**Allowed (never blocked):** read-only inspection (`ps aux | grep mcp_server`),
|
||||
and process management unrelated to the daemons — a `kill` of some other pid is
|
||||
reported as *ambiguous*, not as contamination.
|
||||
|
||||
**What happens on a detected attempt:** the session is marked
|
||||
workflow-contaminated (durable marker, redacted command summary + session id +
|
||||
remote + role). While contaminated, all review / merge / close / completion
|
||||
mutations fail closed. `comment_issue` and `lock_issue` remain allowed so the
|
||||
contaminated worker can post the durable audit comment and hand off.
|
||||
Contamination **cannot be self-cleared** — only a reconciler audit may clear it,
|
||||
and it does not expire with the session-state TTL. The final report must surface
|
||||
the contaminated recovery and must not claim a clean session.
|
||||
|
||||
Operator-authorized host maintenance stays permitted, but the authorization is
|
||||
read from the operator's environment, never from a tool argument: a session must
|
||||
not be able to authorize itself.
|
||||
|
||||
Tooling: call `gitea_record_daemon_process_kill_attempt` to classify/record a
|
||||
proposed command before running it; `gitea_audit_runtime_recovery_contamination`
|
||||
to inspect or (reconciler-only) clear the marker. Full contrast in
|
||||
`docs/mcp-namespace-eof-recovery.md`.
|
||||
|
||||
## Shell Spawn Hard-Stop Rule
|
||||
|
||||
`exit_code: -1` with empty stdout/stderr means the shell failed to spawn — not a
|
||||
|
||||
Reference in New Issue
Block a user