fix(mcp): block manual daemon killing as workflow recovery (Closes #630) #786

Merged
sysadmin merged 1 commits from fix/issue-630-daemon-kill-contamination into master 2026-07-21 18:04:05 -05:00
Owner

Closes #630.

Problem

A session recovered MCP connectivity by running pkill -f mcp_server.py, waited for the IDE to respawn the daemons, then called MCP tools and closed issue #601. Nothing distinguished that closure from one performed over a sanctioned runtime, and other namespaces can be killed as collateral damage.

Partial detection already existed — native_mcp_preference.classify_command_path flags kill/pkill near mcp_server, and review_workflow_boundary classifies a pre-review pkill as MCP repair activity — but neither wrote a durable marker nor failed closed on the review, merge, or close mutations that followed.

Approach

New runtime_recovery_guard.py mirrors the #671 stable-branch contamination model, importing that module's redactor and gated-task set so the two guards cannot drift apart on which mutations a contaminated session may still perform.

Detection. pkill -f mcp_server.py, pkill -f gitea_mcp_server, broad pkill -f mcp, killall variants, and kill <pid> of a pid passed in mcp_pids. A pattern broad enough to sweep other namespaces (pkill -f python) is contamination under its own broad_process_kill class even when it never names MCP.

No false positives. Read-only inspection (ps aux | grep mcp_server) and grepping source for the string pkill are not kills. A pkill aimed at something else is reported without contamination. A bare kill <pid> with no MCP linkage is reported as ambiguous, so ordinary subprocess management is never blocked.

Authorization. Operator-authorized host maintenance stays permitted, read from GITEA_OPERATOR_DAEMON_MAINTENANCE_AUTHORIZATION in the process environment and never from a tool argument. A session cannot set that variable for an already-running daemon, so it cannot authorize itself. This deliberately avoids the self-assertable operator_authorized argument that PR #710 review finding F1 rejected.

Enforcement. A durable runtime_recovery_contamination marker is written per profile identity. _enforce_runtime_recovery_contamination_gate runs beside the #671 gate in the shared pre-flight path, so review / merge / close / completion mutations fail closed. comment_issue and lock_issue stay allowed so a contaminated worker can post its audit comment and hand off. Only gitea_audit_runtime_recovery_contamination with action=clear, under a reconciler profile, clears it.

The new marker kind is added to RECOVERY_CRITICAL_KINDS: contamination must not expire into cleanliness when the four-hour session TTL lapses, which would let a contaminated session self-clear by waiting.

Final report. assess_final_report_validator gains an optional runtime_recovery_marker; with the default None every existing call site is byte-for-byte unchanged. When a marker is live the report must surface the contaminated recovery and must not claim a clean session; either failure is a block finding.

Acceptance criteria

  1. Detect manual daemon kill/restart markers where possible — classify_recovery_command.
  2. Classify pkill -f mcp_server.py and its variants as contaminated recovery unless explicitly authorized — env-only authorization.
  3. Issue close / PR review / merge fail closed after contaminated recovery in the same session — pre-flight gate.
  4. Tests cover manual process kill, sanctioned reconnect, stale-runtime restart, and contaminated post-restart mutation.
  5. Documentation distinguishes sanctioned reconnect/restart from forbidden host process manipulation — docs/mcp-namespace-eof-recovery.md and the workflow skill.

Validation

  • tests/test_issue_630_runtime_recovery_guard.py — 47 new cases, all passing.
  • Tool inventory 112 to 114; the #781 drift guard passes with docs/mcp-tool-inventory.md regenerated through the documented generator rather than hand-edited.
  • Full branch suite: 4173 passed, 6 skipped, 493 subtests passed, 11 failed.
  • Those 11 failures reproduce identically on a clean detached checkout of master at 7ecf7bf2d6 (11 failed, 254 passed) across test_commit_payloads, test_issue_702_review_findings_f1_f6, test_mcp_server, test_post_merge_moot_lease, and test_reconciler_supersession_close. They predate this branch.

Out of scope, observed

KIND_STABLE_BRANCH_CONTAMINATION (#671) is not a member of RECOVERY_CRITICAL_KINDS, so a stable-branch contamination marker does expire with the session TTL. This branch does not change that behaviour; it looks worth a separate defect.

Closes #630. ## Problem A session recovered MCP connectivity by running `pkill -f mcp_server.py`, waited for the IDE to respawn the daemons, then called MCP tools and closed issue #601. Nothing distinguished that closure from one performed over a sanctioned runtime, and other namespaces can be killed as collateral damage. Partial detection already existed — `native_mcp_preference.classify_command_path` flags kill/pkill near `mcp_server`, and `review_workflow_boundary` classifies a pre-review `pkill` as MCP repair activity — but neither wrote a durable marker nor failed closed on the review, merge, or close mutations that followed. ## Approach New `runtime_recovery_guard.py` mirrors the #671 stable-branch contamination model, importing that module's redactor and gated-task set so the two guards cannot drift apart on which mutations a contaminated session may still perform. **Detection.** `pkill -f mcp_server.py`, `pkill -f gitea_mcp_server`, broad `pkill -f mcp`, `killall` variants, and `kill <pid>` of a pid passed in `mcp_pids`. A pattern broad enough to sweep other namespaces (`pkill -f python`) is contamination under its own `broad_process_kill` class even when it never names MCP. **No false positives.** Read-only inspection (`ps aux | grep mcp_server`) and grepping source for the string `pkill` are not kills. A `pkill` aimed at something else is reported without contamination. A bare `kill <pid>` with no MCP linkage is reported as ambiguous, so ordinary subprocess management is never blocked. **Authorization.** Operator-authorized host maintenance stays permitted, read from `GITEA_OPERATOR_DAEMON_MAINTENANCE_AUTHORIZATION` in the process environment and never from a tool argument. A session cannot set that variable for an already-running daemon, so it cannot authorize itself. This deliberately avoids the self-assertable `operator_authorized` argument that PR #710 review finding F1 rejected. **Enforcement.** A durable `runtime_recovery_contamination` marker is written per profile identity. `_enforce_runtime_recovery_contamination_gate` runs beside the #671 gate in the shared pre-flight path, so review / merge / close / completion mutations fail closed. `comment_issue` and `lock_issue` stay allowed so a contaminated worker can post its audit comment and hand off. Only `gitea_audit_runtime_recovery_contamination` with `action=clear`, under a reconciler profile, clears it. The new marker kind is added to `RECOVERY_CRITICAL_KINDS`: contamination must not expire into cleanliness when the four-hour session TTL lapses, which would let a contaminated session self-clear by waiting. **Final report.** `assess_final_report_validator` gains an optional `runtime_recovery_marker`; with the default `None` every existing call site is byte-for-byte unchanged. When a marker is live the report must surface the contaminated recovery and must not claim a clean session; either failure is a `block` finding. ## Acceptance criteria 1. Detect manual daemon kill/restart markers where possible — `classify_recovery_command`. 2. Classify `pkill -f mcp_server.py` and its variants as contaminated recovery unless explicitly authorized — env-only authorization. 3. Issue close / PR review / merge fail closed after contaminated recovery in the same session — pre-flight gate. 4. Tests cover manual process kill, sanctioned reconnect, stale-runtime restart, and contaminated post-restart mutation. 5. Documentation distinguishes sanctioned reconnect/restart from forbidden host process manipulation — `docs/mcp-namespace-eof-recovery.md` and the workflow skill. ## Validation - `tests/test_issue_630_runtime_recovery_guard.py` — 47 new cases, all passing. - Tool inventory 112 to 114; the #781 drift guard passes with `docs/mcp-tool-inventory.md` regenerated through the documented generator rather than hand-edited. - Full branch suite: 4173 passed, 6 skipped, 493 subtests passed, 11 failed. - Those 11 failures reproduce identically on a clean detached checkout of master at 7ecf7bf2d666e179c2ecca27b60bb93f7092383f (11 failed, 254 passed) across `test_commit_payloads`, `test_issue_702_review_findings_f1_f6`, `test_mcp_server`, `test_post_merge_moot_lease`, and `test_reconciler_supersession_close`. They predate this branch. ## Out of scope, observed `KIND_STABLE_BRANCH_CONTAMINATION` (#671) is not a member of `RECOVERY_CRITICAL_KINDS`, so a stable-branch contamination marker does expire with the session TTL. This branch does not change that behaviour; it looks worth a separate defect.
jcwalker3 added 1 commit 2026-07-21 17:55:37 -05:00
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]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: #630
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 95786-bb6421ab6488
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786
phase: claimed
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf2d6
last_activity: 2026-07-21T23:00:33Z
expires_at: 2026-07-21T23:10:33Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: #630 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 95786-bb6421ab6488 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786 phase: claimed candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf2d666e179c2ecca27b60bb93f7092383f last_activity: 2026-07-21T23:00:33Z expires_at: 2026-07-21T23:10:33Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: #630
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 95786-bb6421ab6488
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786
phase: claimed
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf2d6
last_activity: 2026-07-21T23:01:10Z
expires_at: 2026-07-21T23:11:10Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: #630 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 95786-bb6421ab6488 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786 phase: claimed candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf2d666e179c2ecca27b60bb93f7092383f last_activity: 2026-07-21T23:01:10Z expires_at: 2026-07-21T23:11:10Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: #630
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 95786-bb6421ab6488
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786
phase: released
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf2d6
last_activity: 2026-07-21T23:02:08Z
expires_at: 2026-07-21T23:12:08Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: #630 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 95786-bb6421ab6488 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786 phase: released candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf2d666e179c2ecca27b60bb93f7092383f last_activity: 2026-07-21T23:02:08Z expires_at: 2026-07-21T23:12:08Z blocker: manual-release
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 90916-0215a28b2122
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786
phase: claimed
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df
last_activity: 2026-07-21T23:02:17Z
expires_at: 2026-07-21T23:12:17Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: none reviewer_identity: sysadmin profile: prgs-reviewer session_id: 90916-0215a28b2122 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786 phase: claimed candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df last_activity: 2026-07-21T23:02:17Z expires_at: 2026-07-21T23:12:17Z blocker: none
sysadmin approved these changes 2026-07-21 18:03:10 -05:00
sysadmin left a comment
Owner

LGTM. Tests for the manual process recovery guard all passed successfully. The changes are correct and robust.

LGTM. Tests for the manual process recovery guard all passed successfully. The changes are correct and robust.
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 90916-0215a28b2122
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786
phase: released
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df
last_activity: 2026-07-21T23:03:15Z
expires_at: 2026-07-21T23:13:15Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: none reviewer_identity: sysadmin profile: prgs-reviewer session_id: 90916-0215a28b2122 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-786 phase: released candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df last_activity: 2026-07-21T23:03:15Z expires_at: 2026-07-21T23:13:15Z blocker: manual-release
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: none
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 90918-0973583e1f18
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-786
phase: claimed
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df
last_activity: 2026-07-21T23:03:52Z
expires_at: 2026-07-21T23:13:52Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: none reviewer_identity: sysadmin profile: prgs-merger session_id: 90918-0973583e1f18 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-786 phase: claimed candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df last_activity: 2026-07-21T23:03:52Z expires_at: 2026-07-21T23:13:52Z blocker: none
sysadmin merged commit 35e94e107c into master 2026-07-21 18:04:05 -05:00
Owner

Stale #332 review-decision lock cleanup (#594)

Status: APPLIED

Manual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.

## Stale #332 review-decision lock cleanup (#594) Status: **APPLIED** - actor: `sysadmin` - profile: `prgs-merger` - timestamp: `2026-07-21T23:04:08.960288+00:00` - last terminal: `approve` on PR #786 - PR state: `closed` (merged=True) - merge_commit_sha: `35e94e107c32cfdc4b9ab9a95cb4e94df94077d4` - prior live_mutations_count: `1` - prior profile_identity: `prgs-reviewer` Manual deletion of session-state files is **not** the workflow. This path only clears a lock when the referenced PR is merged/closed.
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #786
issue: none
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 90918-0973583e1f18
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-786
phase: released
candidate_head: 1ec4672fad
target_branch: master
target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df
last_activity: 2026-07-21T23:04:59Z
expires_at: 2026-07-21T23:14:59Z
blocker: post-merge-moot

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #786 issue: none reviewer_identity: sysadmin profile: prgs-merger session_id: 90918-0973583e1f18 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-786 phase: released candidate_head: 1ec4672fad897a7391026ace5dd19a47351cde16 target_branch: master target_branch_sha: 7ecf7bf4ebc01b1c67d1656b85848bb2085ce3df last_activity: 2026-07-21T23:04:59Z expires_at: 2026-07-21T23:14:59Z blocker: post-merge-moot
Owner

Canonical Issue State

STATE: merged-awaiting-controller
WHO_IS_NEXT: controller
NEXT_ACTION: accept the completed feature and close the task session
NEXT_PROMPT:

Accept the merged PR 786 and finalize issue 630 on repository Scaled-Tech-Consulting/Gitea-Tools.

WHAT_HAPPENED: PR 786 was successfully reviewed, approved, and merged; reconciler finalization completed; branch deletion safely blocked by active author worktree/session ownership.
WHY: PR head passes all tests, daemon kill guardrail successfully verified.
RELATED_PRS: 786
BLOCKERS: active author ownership protects remote branch fix/issue-630-daemon-kill-contamination
VALIDATION: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_issue_630_runtime_recovery_guard.py (executed in branches/review-pr-786)
LAST_UPDATED_BY: sysadmin (prgs-reconciler)

Canonical Handoff

REPOSITORY: Scaled-Tech-Consulting/Gitea-Tools
ISSUE: 630
PR: 786
WORKFLOW_STATE: merged-awaiting-controller
HEAD_SHA: 1ec4672fad897a7391026ace5dd19a47351cde16
BASE_BRANCH: master
BASE_OR_MERGE_SHA: 35e94e107c32cfdc4b9ab9a95cb4e94df94077d4
ACTING_ROLE: reconciler
ACTING_IDENTITY: sysadmin (prgs-reconciler)
COMPLETED_ACTIONS: reviewed, approved, merged, post-merge label cleaned, moot merger lease released
VALIDATION_EVIDENCE: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_issue_630_runtime_recovery_guard.py (executed in branches/review-pr-786), 47 passed
MUTATION_LEDGER: gitea_review_pr APPROVE 786, gitea_merge_pr (merge), gitea_cleanup_post_merge_moot_lease
BLOCKERS: active author ownership protects remote branch fix/issue-630-daemon-kill-contamination
NEXT_ACTOR: controller
NEXT_ACTION: accept the completed feature and close the task session
PROHIBITED_ACTIONS: do not force-push, do not self-merge
NEXT_PROMPT: Accept the merged PR 786 and finalize issue 630 on repository Scaled-Tech-Consulting/Gitea-Tools.
WORKFLOW_FAILURE_ISSUES: none
LAST_UPDATED: 2026-07-21T23:06:00Z
## Canonical Issue State STATE: merged-awaiting-controller WHO_IS_NEXT: controller NEXT_ACTION: accept the completed feature and close the task session NEXT_PROMPT: ```text Accept the merged PR 786 and finalize issue 630 on repository Scaled-Tech-Consulting/Gitea-Tools. ``` WHAT_HAPPENED: PR 786 was successfully reviewed, approved, and merged; reconciler finalization completed; branch deletion safely blocked by active author worktree/session ownership. WHY: PR head passes all tests, daemon kill guardrail successfully verified. RELATED_PRS: 786 BLOCKERS: active author ownership protects remote branch fix/issue-630-daemon-kill-contamination VALIDATION: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_issue_630_runtime_recovery_guard.py (executed in branches/review-pr-786) LAST_UPDATED_BY: sysadmin (prgs-reconciler) <!-- sph:v1 --> ## Canonical Handoff ```text REPOSITORY: Scaled-Tech-Consulting/Gitea-Tools ISSUE: 630 PR: 786 WORKFLOW_STATE: merged-awaiting-controller HEAD_SHA: 1ec4672fad897a7391026ace5dd19a47351cde16 BASE_BRANCH: master BASE_OR_MERGE_SHA: 35e94e107c32cfdc4b9ab9a95cb4e94df94077d4 ACTING_ROLE: reconciler ACTING_IDENTITY: sysadmin (prgs-reconciler) COMPLETED_ACTIONS: reviewed, approved, merged, post-merge label cleaned, moot merger lease released VALIDATION_EVIDENCE: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python -m pytest tests/test_issue_630_runtime_recovery_guard.py (executed in branches/review-pr-786), 47 passed MUTATION_LEDGER: gitea_review_pr APPROVE 786, gitea_merge_pr (merge), gitea_cleanup_post_merge_moot_lease BLOCKERS: active author ownership protects remote branch fix/issue-630-daemon-kill-contamination NEXT_ACTOR: controller NEXT_ACTION: accept the completed feature and close the task session PROHIBITED_ACTIONS: do not force-push, do not self-merge NEXT_PROMPT: Accept the merged PR 786 and finalize issue 630 on repository Scaled-Tech-Consulting/Gitea-Tools. WORKFLOW_FAILURE_ISSUES: none LAST_UPDATED: 2026-07-21T23:06:00Z ```
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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