Block manual MCP daemon process killing as workflow recovery #630

Closed
opened 2026-07-10 14:35:32 -05:00 by jcwalker3 · 3 comments
Owner

Problem

A session attempted to recover MCP connectivity by running pkill -f mcp_server.py, then waited for the IDE to restart the daemons. This bypasses sanctioned reconnect/restart tooling, can kill unrelated namespaces, can contaminate runtime proof, and makes later review/merge/closure decisions untrustworthy.

Observed behavior

  • Session ran ps aux | grep mcp_server.
  • Session ran pkill -f mcp_server.py.
  • Session later called MCP tools after daemons restarted.
  • Same session closed Issue #601.
  • The run also touched root .env state and reported local commits without clear PR/merge proof.

Impact

  • Unrelated MCP namespaces can be killed as collateral damage.
  • Session-local runtime proof becomes untrustworthy after host process manipulation.
  • Close/review/merge actions performed after contaminated recovery may appear clean while lacking a sanctioned recovery chain.
  • Operators cannot distinguish IDE-mediated reconnect from LLM-initiated process kills.
  • Partial detectors already exist (native_mcp_preference.py kill/pkill pattern; review_workflow_boundary.py classifies pkill as MCP repair activity) but do not fully fail-closed on post-kill mutations or final-report cleanliness.

Required behavior

  • Workflow recovery must use sanctioned reconnect/restart tooling only (host auto-reconnect, operator-owned restart paths, or explicit sanctioned restart tools — not ad-hoc shell process kills).
  • Manual host process killing must be detected and classified as workflow contamination unless explicitly operator-authorized.
  • Any review, merge, close, or proof following manual daemon process killing must be blocked pending controller/reconciler audit.
  • The final report must identify process manipulation and forbid treating the session as clean.

Scope

  1. Detect manual daemon kill/restart markers where possible (shell command patterns, session audit markers, process-lifecycle anomalies relative to sanctioned restart).
  2. Classify pkill -f mcp_server.py, kill of MCP pids, broad pkill -f mcp, and equivalents as contaminated recovery unless operator authorization proof is present.
  3. Fail closed for issue close, PR review, and merge tools when the same session has recorded contaminated runtime recovery.
  4. Final-report / validation ledger rules: contaminated recovery must surface explicitly; clean-session claims must be rejected.
  5. Docs: clearly distinguish sanctioned reconnect/restart from forbidden host process manipulation.

Related issues (not duplicates)

  • #584 — transport closed recovery via host auto-reconnect (sanctioned/verify-only; contrasts with pkill).
  • #591 — auto-restart namespaces when master advances (sanctioned path; closed).
  • #531 — detect stale MCP runtime before mutation (closed; incomplete for process-kill contamination).
  • #610 — resolver vs master-parity stale-runtime disagreement (open; related runtime safety, different fault).
  • #558 — block unsanctioned direct imports/raw API mutation paths (closed; complementary trust-boundary work).
  • #539 — reviewer session performed unauthorized mutations / API bypass (closed; contamination class, different vector).

Acceptance criteria

  1. Workflow tools detect manual daemon kill/restart markers where possible.
  2. Reports must classify pkill -f mcp_server.py or equivalent as contaminated recovery unless explicitly authorized.
  3. Issue close/review/merge tools fail closed after contaminated runtime recovery in the same session.
  4. Tests cover manual process kill, sanctioned reconnect, stale runtime restart, and contaminated post-restart mutation.
  5. Documentation clearly distinguishes sanctioned reconnect/restart from forbidden host process manipulation.

Non-goals

  • Do not forbid operator-authorized host maintenance outside LLM workflow sessions when explicitly authorized and recorded.
  • Do not replace IDE/host auto-reconnect with a new process-kill path.
  • Do not re-open #601 lifecycle work; use it only as observed-evidence context.
  • Do not solve unrelated stale-runtime parity (#610) inside this issue unless needed to wire contamination markers.

Evidence note

Issue #601 closure in a session that performed pkill -f mcp_server.py is a concrete contamination example. Treat it as evidence of the class of defect, not as sole implementation scope.

Label note

Requested labels contamination and type:bug are not present on the repository label set. Applied closest existing set: type:guardrail, bug, workflow-hardening, stale-runtime, mcp-health, anti-stomp, status:ready. Controllers may add contamination / type:bug after taxonomy extension (#603).

## Problem A session attempted to recover MCP connectivity by running `pkill -f mcp_server.py`, then waited for the IDE to restart the daemons. This bypasses sanctioned reconnect/restart tooling, can kill unrelated namespaces, can contaminate runtime proof, and makes later review/merge/closure decisions untrustworthy. ## Observed behavior * Session ran `ps aux | grep mcp_server`. * Session ran `pkill -f mcp_server.py`. * Session later called MCP tools after daemons restarted. * Same session closed Issue #601. * The run also touched root `.env` state and reported local commits without clear PR/merge proof. ## Impact * Unrelated MCP namespaces can be killed as collateral damage. * Session-local runtime proof becomes untrustworthy after host process manipulation. * Close/review/merge actions performed after contaminated recovery may appear clean while lacking a sanctioned recovery chain. * Operators cannot distinguish IDE-mediated reconnect from LLM-initiated process kills. * Partial detectors already exist (`native_mcp_preference.py` kill/pkill pattern; `review_workflow_boundary.py` classifies `pkill` as MCP repair activity) but do not fully fail-closed on post-kill mutations or final-report cleanliness. ## Required behavior * Workflow recovery must use **sanctioned reconnect/restart tooling only** (host auto-reconnect, operator-owned restart paths, or explicit sanctioned restart tools — not ad-hoc shell process kills). * Manual host process killing must be detected and classified as **workflow contamination** unless explicitly operator-authorized. * Any review, merge, close, or proof following manual daemon process killing must be **blocked pending controller/reconciler audit**. * The final report must identify process manipulation and **forbid treating the session as clean**. ## Scope 1. Detect manual daemon kill/restart markers where possible (shell command patterns, session audit markers, process-lifecycle anomalies relative to sanctioned restart). 2. Classify `pkill -f mcp_server.py`, `kill` of MCP pids, broad `pkill -f mcp`, and equivalents as contaminated recovery unless operator authorization proof is present. 3. Fail closed for issue close, PR review, and merge tools when the same session has recorded contaminated runtime recovery. 4. Final-report / validation ledger rules: contaminated recovery must surface explicitly; clean-session claims must be rejected. 5. Docs: clearly distinguish sanctioned reconnect/restart from forbidden host process manipulation. ## Related issues (not duplicates) * #584 — transport closed recovery via **host auto-reconnect** (sanctioned/verify-only; contrasts with pkill). * #591 — auto-restart namespaces when master advances (sanctioned path; closed). * #531 — detect stale MCP runtime before mutation (closed; incomplete for process-kill contamination). * #610 — resolver vs master-parity stale-runtime disagreement (open; related runtime safety, different fault). * #558 — block unsanctioned direct imports/raw API mutation paths (closed; complementary trust-boundary work). * #539 — reviewer session performed unauthorized mutations / API bypass (closed; contamination class, different vector). ## Acceptance criteria 1. Workflow tools detect manual daemon kill/restart markers where possible. 2. Reports must classify `pkill -f mcp_server.py` or equivalent as contaminated recovery unless explicitly authorized. 3. Issue close/review/merge tools fail closed after contaminated runtime recovery in the same session. 4. Tests cover manual process kill, sanctioned reconnect, stale runtime restart, and contaminated post-restart mutation. 5. Documentation clearly distinguishes sanctioned reconnect/restart from forbidden host process manipulation. ## Non-goals * Do not forbid operator-authorized host maintenance outside LLM workflow sessions when explicitly authorized and recorded. * Do not replace IDE/host auto-reconnect with a new process-kill path. * Do not re-open #601 lifecycle work; use it only as observed-evidence context. * Do not solve unrelated stale-runtime parity (#610) inside this issue unless needed to wire contamination markers. ## Evidence note Issue #601 closure in a session that performed `pkill -f mcp_server.py` is a concrete contamination example. Treat it as evidence of the class of defect, not as sole implementation scope. ## Label note Requested labels `contamination` and `type:bug` are not present on the repository label set. Applied closest existing set: `type:guardrail`, `bug`, `workflow-hardening`, `stale-runtime`, `mcp-health`, `anti-stomp`, `status:ready`. Controllers may add `contamination` / `type:bug` after taxonomy extension (#603).
Author
Owner

Canonical Issue State

STATE:
ready-for-author

WHO_IS_NEXT:
author

NEXT_ACTION:
Keep this issue scoped to pkill/contamination detection; integrate with umbrella #655 (not a duplicate); coordinator/break-glass live under #655–#669

NEXT_PROMPT:

AUTHOR prgs Gitea-Tools: Issue #630 remains the contamination detector for manual daemon kills.
Governed restart coordination is umbrella #655 (children #656–#669). Implement #630 without inventing a full coordinator—link to #655 for authorized restart paths.
Vision #652 · Roadmap #653.

WHAT_HAPPENED:
Linked #630 as complementary to #655 restart governance program.

WHY:
Avoid duplicate ownership; pkill ban ≠ full drain/coordinator.

RELATED_ISSUES:
#655 #657 #664 #665 #652 #653

RELATED_PRS:
none

BLOCKERS:
none

VALIDATION:
not a duplicate of #655

LAST_UPDATED_BY:
jcwalker3 / prgs-author / author / 2026-07-10

## Canonical Issue State STATE: ready-for-author WHO_IS_NEXT: author NEXT_ACTION: Keep this issue scoped to pkill/contamination detection; integrate with umbrella #655 (not a duplicate); coordinator/break-glass live under #655–#669 NEXT_PROMPT: ```text AUTHOR prgs Gitea-Tools: Issue #630 remains the contamination detector for manual daemon kills. Governed restart coordination is umbrella #655 (children #656–#669). Implement #630 without inventing a full coordinator—link to #655 for authorized restart paths. Vision #652 · Roadmap #653. ``` WHAT_HAPPENED: Linked #630 as complementary to #655 restart governance program. WHY: Avoid duplicate ownership; pkill ban ≠ full drain/coordinator. RELATED_ISSUES: #655 #657 #664 #665 #652 #653 RELATED_PRS: none BLOCKERS: none VALIDATION: not a duplicate of #655 LAST_UPDATED_BY: jcwalker3 / prgs-author / author / 2026-07-10
jcwalker3 added status:in-progress and removed status:ready labels 2026-07-21 17:37:05 -05:00
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #630 - branch: fix/issue-630-daemon-kill-contamination - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
jcwalker3 added status:pr-open and removed status:in-progress labels 2026-07-21 17:55:37 -05:00
Author
Owner

[THREAD STATE LEDGER] Issue #630 manual daemon-kill contamination — author implementation complete, awaiting independent reviewer

What is true now:

  • Server-side decision state: no review verdict has been recorded via the review API for PR #786; no server-side review or merge state exists yet.
  • Local verdict/state: the implementation is committed and published at head 1ec4672fad897a7391026ace5dd19a47351cde16; PR #786 is in open state against base master 7ecf7bf2d6, and Gitea reports mergeable true.
  • Latest known validation: the full suite in the branch worktree gave 4173 passed, 6 skipped, 493 subtests passed, 11 failed; those same 11 failures reproduce identically on a clean detached checkout of master at 7ecf7bf2 (11 failed, 254 passed), so they predate this branch.

What changed:

  • Added runtime_recovery_guard.py: classification of kill/pkill/killall commands and known-pid kills, operator authorization read from the process environment only, the contamination record builder, the mutation gate, and the final-report rules.
  • Added the runtime_recovery_contamination session-state kind and placed it in RECOVERY_CRITICAL_KINDS, so contamination cannot expire into cleanliness with the four-hour TTL.
  • Wired _enforce_runtime_recovery_contamination_gate into the shared pre-flight path beside the #671 stable-branch gate.
  • Registered gitea_record_daemon_process_kill_attempt and gitea_audit_runtime_recovery_contamination; docs/mcp-tool-inventory.md was regenerated from 112 to 114 entries through the documented generator for the #781 drift guard.
  • assess_final_report_validator gained an optional runtime_recovery_marker parameter; its default of None leaves every existing call site byte-for-byte unchanged.
  • Documented the sanctioned-versus-forbidden contrast in docs/mcp-namespace-eof-recovery.md and skills/llm-project-workflow/SKILL.md.
  • Added tests/test_issue_630_runtime_recovery_guard.py with 47 cases.

What is blocked:

  • Blocker classification: no blocker
  • The gated-task set and the command redactor are imported from stable_branch_push_guard rather than redefined, so the two contamination models cannot diverge on which mutations a contaminated session may still perform.

Who/what acts next:

  • Next actor: reviewer
  • Required action: acquire a reviewer PR lease on PR #786 pinned at head 1ec4672fad, load the review workflow, and record a formal review verdict through the native reviewer namespace.
  • Do not do: do not review or merge from this author session; do not widen scope into the #671 TTL gap noted at the end of the PR body; do not perform branch or worktree cleanup outside this issue.

Canonical Issue State

STATE:
implementation-complete-awaiting-review

WHO_IS_NEXT:
reviewer

NEXT_ACTION:
Perform an independent formal review of PR #786 at head 1ec4672fad and record the verdict through the reviewer namespace.

NEXT_PROMPT:

Role: REVIEWER
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
PR: 786 (Closes issue 630)
Pinned head: 1ec4672fad897a7391026ace5dd19a47351cde16
Base: master 7ecf7bf2d666e179c2ecca27b60bb93f7092383f

Invoke the gitea-workflow skill first. Fail closed on preflight: gitea_whoami on the
gitea-reviewer namespace, gitea_assess_master_parity, gitea_load_review_workflow, then
gitea_resolve_task_capability immediately before each reviewer mutation.

Scope to verify:
1. Detection covers pkill -f mcp_server.py, pkill -f gitea_mcp_server, broad pkill -f mcp,
   killall variants, and kill of a pid supplied through mcp_pids.
2. A broad sweep such as pkill -f python is contamination under broad_process_kill even
   though it never names MCP.
3. No false positives: ps aux | grep mcp_server, grepping source for the pkill string, a
   pkill aimed elsewhere, and a bare kill of an unknown pid must not contaminate.
4. Operator authorization is reachable only through the process environment variable and
   cannot be self-asserted through a tool argument.
5. The gate fails closed for review, merge, close and completion tasks while leaving
   comment_issue and lock_issue reachable, and exempts only the reconciler role.
6. Clearing requires a reconciler profile, and the marker kind is recovery-critical so it
   does not expire with the session TTL.
7. Final-report rules reject a clean-session claim and require the contamination to be
   surfaced, while the default None parameter leaves existing callers unchanged.
8. docs/mcp-tool-inventory.md lists both new tools and the #781 drift guard passes.

Verification claimed by the author, to be checked rather than assumed: 47 cases in
tests/test_issue_630_runtime_recovery_guard.py; branch suite 4173 passed with 11 failures
in test_commit_payloads, test_issue_702_review_findings_f1_f6, test_mcp_server,
test_post_merge_moot_lease and test_reconciler_supersession_close that reproduce on a clean
detached checkout of master at 7ecf7bf2.

Prohibited: approving your own authored work; merging in the reviewer session; widening
scope into the #671 TTL gap; branch or worktree cleanup outside this issue.

WHAT_HAPPENED:
The canonical allocator selected issue #630 for the author role after umbrella #628 was excluded as multi-PR work. The issue was locked to branch fix/issue-630-daemon-kill-contamination with an isolated worktree, implemented, and published as PR #786. The design mirrors the #671 stable-branch contamination guard so the two share one gated-task set and one redactor.

WHY:
A session that killed the MCP daemons and then closed issue #601 left nothing to distinguish that closure from one performed over a sanctioned runtime. The existing detectors produced advisory strings only: no durable marker, no fail-closed behaviour on the review, merge or close mutations that followed, and no rule preventing the final report from claiming the session was clean.

RELATED_PRS:

BLOCKERS:
none

VALIDATION:

  • 47 new cases covering detection of each kill form, broad-sweep classification, the four no-false-positive scenarios, environment-only authorization, redaction of secrets in the stored summary, the gate over each task class, reconciler exemption, reconciler-only clearing, recovery-critical marker membership, both MCP tools, and the final-report rules
  • branch worktree suite: 4173 passed, 6 skipped, 493 subtests passed, 11 failed
  • baseline detached checkout of master at 7ecf7bf2: 11 failed, 254 passed across the same five files, proving those failures predate this branch
  • read-after-write on the published branch: remote head 1ec4672fad with a tree identical to the local commit
  • no review verdict recorded; no merge performed

LAST_UPDATED_BY:
jcwalker3 / prgs-author / author session / 2026-07-21

[THREAD STATE LEDGER] Issue #630 manual daemon-kill contamination — author implementation complete, awaiting independent reviewer What is true now: - Server-side decision state: no review verdict has been recorded via the review API for PR #786; no server-side review or merge state exists yet. - Local verdict/state: the implementation is committed and published at head 1ec4672fad897a7391026ace5dd19a47351cde16; PR #786 is in open state against base master 7ecf7bf2d666e179c2ecca27b60bb93f7092383f, and Gitea reports mergeable true. - Latest known validation: the full suite in the branch worktree gave 4173 passed, 6 skipped, 493 subtests passed, 11 failed; those same 11 failures reproduce identically on a clean detached checkout of master at 7ecf7bf2 (11 failed, 254 passed), so they predate this branch. What changed: - Added runtime_recovery_guard.py: classification of kill/pkill/killall commands and known-pid kills, operator authorization read from the process environment only, the contamination record builder, the mutation gate, and the final-report rules. - Added the runtime_recovery_contamination session-state kind and placed it in RECOVERY_CRITICAL_KINDS, so contamination cannot expire into cleanliness with the four-hour TTL. - Wired _enforce_runtime_recovery_contamination_gate into the shared pre-flight path beside the #671 stable-branch gate. - Registered gitea_record_daemon_process_kill_attempt and gitea_audit_runtime_recovery_contamination; docs/mcp-tool-inventory.md was regenerated from 112 to 114 entries through the documented generator for the #781 drift guard. - assess_final_report_validator gained an optional runtime_recovery_marker parameter; its default of None leaves every existing call site byte-for-byte unchanged. - Documented the sanctioned-versus-forbidden contrast in docs/mcp-namespace-eof-recovery.md and skills/llm-project-workflow/SKILL.md. - Added tests/test_issue_630_runtime_recovery_guard.py with 47 cases. What is blocked: - Blocker classification: no blocker - The gated-task set and the command redactor are imported from stable_branch_push_guard rather than redefined, so the two contamination models cannot diverge on which mutations a contaminated session may still perform. Who/what acts next: - Next actor: reviewer - Required action: acquire a reviewer PR lease on PR #786 pinned at head 1ec4672fad897a7391026ace5dd19a47351cde16, load the review workflow, and record a formal review verdict through the native reviewer namespace. - Do not do: do not review or merge from this author session; do not widen scope into the #671 TTL gap noted at the end of the PR body; do not perform branch or worktree cleanup outside this issue. ## Canonical Issue State STATE: implementation-complete-awaiting-review WHO_IS_NEXT: reviewer NEXT_ACTION: Perform an independent formal review of PR #786 at head 1ec4672fad897a7391026ace5dd19a47351cde16 and record the verdict through the reviewer namespace. NEXT_PROMPT: ```text Role: REVIEWER Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools PR: 786 (Closes issue 630) Pinned head: 1ec4672fad897a7391026ace5dd19a47351cde16 Base: master 7ecf7bf2d666e179c2ecca27b60bb93f7092383f Invoke the gitea-workflow skill first. Fail closed on preflight: gitea_whoami on the gitea-reviewer namespace, gitea_assess_master_parity, gitea_load_review_workflow, then gitea_resolve_task_capability immediately before each reviewer mutation. Scope to verify: 1. Detection covers pkill -f mcp_server.py, pkill -f gitea_mcp_server, broad pkill -f mcp, killall variants, and kill of a pid supplied through mcp_pids. 2. A broad sweep such as pkill -f python is contamination under broad_process_kill even though it never names MCP. 3. No false positives: ps aux | grep mcp_server, grepping source for the pkill string, a pkill aimed elsewhere, and a bare kill of an unknown pid must not contaminate. 4. Operator authorization is reachable only through the process environment variable and cannot be self-asserted through a tool argument. 5. The gate fails closed for review, merge, close and completion tasks while leaving comment_issue and lock_issue reachable, and exempts only the reconciler role. 6. Clearing requires a reconciler profile, and the marker kind is recovery-critical so it does not expire with the session TTL. 7. Final-report rules reject a clean-session claim and require the contamination to be surfaced, while the default None parameter leaves existing callers unchanged. 8. docs/mcp-tool-inventory.md lists both new tools and the #781 drift guard passes. Verification claimed by the author, to be checked rather than assumed: 47 cases in tests/test_issue_630_runtime_recovery_guard.py; branch suite 4173 passed with 11 failures in test_commit_payloads, test_issue_702_review_findings_f1_f6, test_mcp_server, test_post_merge_moot_lease and test_reconciler_supersession_close that reproduce on a clean detached checkout of master at 7ecf7bf2. Prohibited: approving your own authored work; merging in the reviewer session; widening scope into the #671 TTL gap; branch or worktree cleanup outside this issue. ``` WHAT_HAPPENED: The canonical allocator selected issue #630 for the author role after umbrella #628 was excluded as multi-PR work. The issue was locked to branch fix/issue-630-daemon-kill-contamination with an isolated worktree, implemented, and published as PR #786. The design mirrors the #671 stable-branch contamination guard so the two share one gated-task set and one redactor. WHY: A session that killed the MCP daemons and then closed issue #601 left nothing to distinguish that closure from one performed over a sanctioned runtime. The existing detectors produced advisory strings only: no durable marker, no fail-closed behaviour on the review, merge or close mutations that followed, and no rule preventing the final report from claiming the session was clean. RELATED_PRS: - PR #786 (this change, in open state at head 1ec4672fad897a7391026ace5dd19a47351cde16) BLOCKERS: none VALIDATION: - 47 new cases covering detection of each kill form, broad-sweep classification, the four no-false-positive scenarios, environment-only authorization, redaction of secrets in the stored summary, the gate over each task class, reconciler exemption, reconciler-only clearing, recovery-critical marker membership, both MCP tools, and the final-report rules - branch worktree suite: 4173 passed, 6 skipped, 493 subtests passed, 11 failed - baseline detached checkout of master at 7ecf7bf2: 11 failed, 254 passed across the same five files, proving those failures predate this branch - read-after-write on the published branch: remote head 1ec4672fad897a7391026ace5dd19a47351cde16 with a tree identical to the local commit - no review verdict recorded; no merge performed LAST_UPDATED_BY: jcwalker3 / prgs-author / author session / 2026-07-21
sysadmin removed the status:pr-open label 2026-07-21 18:04:08 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#630