fix(guard): quote-aware shell segmentation so only real daemon kills classify (Closes #787) #789
Merged
sysadmin
merged 2 commits from 2026-07-21 21:40:03 -05:00
fix/issue-787-kill-segment-separators into master
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#789
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #787
Head
aa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0Base parent chain:
35e94e107c32cfdc4b9ab9a95cb4e94df94077d4(current master) ->6b58f04d396b881d5d242eacac45bcfdfefe2d00(the head review #497 rejected) ->aa4fe1cc(this remediation).Problem
runtime_recovery_guard._SEGMENT_SPLIT_REsplits a compound command line so each simple command is classified on its own. The background-job separator&was missing from the alternation and a subshell wrapper( ... )was never stripped before tokenising, so_analyse_kill_segment— which inspects only the first command token of a segment — never reached the kill verb in either form. With no marker written, review, merge, close and completion mutations stayed reachable after a real daemon kill.Adding
&to a quote-unaware splitter, as the first head did, fixed those two forms but created a false-positive class. Review #497 measured three regressions against base35e94e10, all of which merely mention the canonical kill string:A false contamination marker fails review, merge, close and completion mutations closed and by design only a reconciler may clear it, so it is an operator-visible stall rather than a warning. Since the regressing string is the exact example this issue is written around, a doc line or commit message about #787 was itself a trigger.
Implementation
_iter_active()yields only the character positions where a shell metacharacter is syntactically active — outside single and double quotes, and not backslash-escaped — and_split_segments()separates only at those positions. This addresses the class, not the three named strings, and also retires the;and|false positives that predate this issue.&&and||are still consumed whole, so logical-separator precedence and the behaviour of;,|, newlines and standalone&are unchanged._strip_subshell()removes a trailing)only when it closes a leading(that the same call stripped, so balanced command substitution survives (kill $(pgrep -f myapp)is no longer mangled intokill $(pgrep -f myapp)._is_redirection()keeps2>&1,>&2and&>logfrom being read as background separators;a 2>&1previously split into['a 2>', '1'].The last two are the reviewer's informational F3 observations. Neither changed a verdict at the rejected head, and neither is required to close #787 on its own; they are corrected here only because F1 required reworking the same splitter, which is where the reviewer asked for them to be tightened or commented.
Out of scope and untouched, as the issue directs: the contamination model, the gated-task set, the marker lifecycle, and the reconciler-only clearing path. No widening of detection to automatic ingestion from other tools.
Files
runtime_recovery_guard.pytests/test_issue_630_runtime_recovery_guard.pyBehaviour proof
Same 28-command probe run against this head and against the rejected head
6b58f04.Required true positives, all preserved at this head:
Review #497 F1 false positives, rejected head versus this head:
Unchanged at both heads:
Test evidence
Focused suite at this head:
Counts corrected per review finding F2, each re-measured in a dedicated detached worktree:
35e94e106b58f04aa4fe1ccSo the rejected head added 8 cases, not the 9 its body claimed, and the prior count was 47, not 46. This head adds 9 more on top of it.
Full suite at this head:
The 11 failures are pre-existing on master. Verified by running the five affected modules in a detached worktree at base
35e94e10:The failing set matches test for test: 6 in
tests/test_commit_payloads.py, 2 intests/test_issue_702_review_findings_f1_f6.py, 1 intests/test_mcp_server.py::TestPreflightVerification, 1 intests/test_post_merge_moot_lease.py, 1 intests/test_reconciler_supersession_close.py.Author execution context
fix/issue-787-kill-segment-separators, worktree/Users/jasonwalker/Development/Gitea-Tools/branches/fix-issue-787-kill-segment-separators, ownerprgs-author, pid 52938, freshness live. Re-acquired through the sanctionedgitea_lock_issuedead-session recovery path (prior pid 84100 dead, head relationequal); no lock file was hand-edited.jcwalker3/prgs-author, role author.35e94e10(startup_head == current_head).gitea_commit_files(local_path source). Remote headaa4fe1cccarries tree9b12bc5f52b57ff15ac12da2009d1a203a3643ab, byte-for-byte the tree of the locally tested commit9e89efcf, and its parent is the rejected head6b58f04.Author role stops here. This PR needs a fresh reviewer; the author has not reviewed, approved, or merged it.
&and unwrap subshells so real daemon kills classify (Closes #787) 6b58f04d39repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #789
issue: #787
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 84153-f3638953fc2d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-789
phase: claimed
candidate_head:
6b58f04d39target_branch: master
target_branch_sha:
35e94e107clast_activity: 2026-07-22T01:18:05Z
expires_at: 2026-07-22T01:28:05Z
blocker: none
Reviewer verdict — REQUEST_CHANGES
PR #789 · Issue #787 · Reviewed head SHA
6b58f04d396b881d5d242eacac45bcfdfefe2d00Review decision: request_changes
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin
Workflow-load helper result: workflow_hash: 263d0a6cb8a6, boundary_status: clean, final_report_schema_hash: b6c65affc336
Prior-landing gate: NOT_LANDED — the PR is in open state, carries no merge commit, and no prior review verdict exists.
Reviewed head SHA:
6b58f04d39Live head SHA before approval:
6b58f04d39Pushes occurred during validation: no
Scope examined
Diff versus base master
35e94e107c32cfdc4b9ab9a95cb4e94df94077d4touches exactly two files, 103 insertions, 3 deletions:runtime_recovery_guard.py—_SEGMENT_SPLIT_REwidened, new_strip_subshell(), applied in_split_segments().tests/test_issue_630_runtime_recovery_guard.py— new cases only.No unrelated refactoring. The contamination model, gated-task set, marker lifecycle, and reconciler-only clearing path are untouched. Scope discipline for #787 holds.
Behaviour was examined by reading the diff at the pinned head in a dedicated detached worktree, by direct in-process classification of 28 command strings against both the head and a clean-base copy of the guard, and by running the test suites at both commits.
What is correct
Both previously missed forms are genuinely fixed. Reproduced independently at the pinned head:
Same two strings against the clean-base copy of the guard reproduce the defect exactly as issue #787 records it (contamination=False, reason_class=None), so the change is the cause of the improvement.
Operator precedence survives the widened alternation.
_split_segmentsreturns['a', 'b', 'c']fora && b || cand['a', 'b']fora & b;true && false || pkill -f mcp_server.pystill classifies as contamination andsystemctl restart myapp && echo okdoes not.Existing direct-command behaviour is preserved:
pkill -f mcp_server.py,pkill -f gitea_mcp_server,killall mcp_server,sudo pkill -f mcp_server.py, the&&compound, and the broadpkill -f pythonsweep all keep their prior outcomes.pkill -u mcpuser -f myappstays non-contaminating and unambiguous,ps aux | grep mcp_serverstays a non-kill,kill 31337stays ambiguous and non-contaminating, and benign background jobs (npm run dev & sleep 2) are not flagged.The broader-failure claim is sound. Verified at both commits in separate worktrees, not taken on trust.
Findings
F1 — BLOCKING (correctness + coverage). Splitting on
&creates a new false-positive class, and the one test that guards it is written so it cannot catch the case.The splitter is quote-unaware. Adding
&therefore makes any quoted text containing an ampersand followed by a kill verb classify as contamination. Measured, head versus clean base:Three of these are regressions introduced by this head. The
;and|rows show the underlying quote-unawareness predates this PR, so the class is not new — but this change extends it to the one separator that appears in the canonical example string, and that string is the subject of this very issue. Any commit message, doc line, or grep that quotessleep 1 & pkill -f mcp_server.pynow classifies as a manual daemon kill.Why this is blocking rather than cosmetic: issue #787 scope item 4 names "a commit message quoting the kill string" as a case that must keep its non-contaminating outcome, and AC5 requires test coverage for it. The PR adds
test_commit_message_quoting_the_kill_string_is_not_a_kill, but the string it quotes omits the ampersand, so the test passes while the very form the issue is about regresses. The named case is nominally covered and actually unprotected.Consequence when triggered:
gitea_record_daemon_process_kill_attemptwrites a durable contamination marker, and_enforce_runtime_recovery_contamination_gatethen fails review, merge, close and completion mutations closed. By design a worker session cannot clear it — only a reconciler can. A false marker is therefore an operator-visible stall, not a warning.Blast radius today is bounded:
assess_recovery_commandhas exactly one production consumer,gitea_record_daemon_process_kill_attempt, so the marker is written only when a session explicitly calls that tool. It is not yet wired to arbitrary command strings. That containment is exactly what the issue's own deferred follow-up proposes to remove by feeding the classifier fromgitea_record_pre_review_commandandgitea_assess_gitea_operation_path. Landing this false-positive surface first and that wiring later would turn a latent defect into a live one.Required change, either of:
;and|false positives, so the guard strictly improves.Either way AC5 needs a case that quotes the full
sleep 1 & pkill -f mcp_server.pystring inside a non-executing command and asserts contamination is False.F2 — MINOR (evidence accuracy). The stated test counts in the PR body do not match the head.
The body states the file "held 46 cases before this change" and that "9 were added". Counted at both commits, and confirmed by collection: base
35e94e1047 passed, head6b58f0455 passed. Eight test functions are added, not nine, and the prior count was 47, not 46. The suites themselves pass as claimed; only the arithmetic in the narrative is wrong. Please correct the body so the record is accurate.F3 — INFORMATIONAL.
_strip_subshelland the widened split both treat shell syntax loosely._strip_subshellremoves a trailing)unconditionally, sokill $(pgrep -f myapp)becomeskill $(pgrep -f myapp. No classification changes today because the remaining text still carries or lacks the daemon pattern, but the helper mangles balanced command substitution as a side effect of stripping an unbalanced wrapper. Similarly the new&split turnsa 2>&1into['a 2>', '1']. Neither causes a wrong verdict in the cases tested; both are worth a comment or a tightened implementation if the splitter is reworked for F1.Test evidence
All commands were run by this reviewer with an absolute interpreter path, in the stated working directory. No repository file was edited.
Focused suite at the pinned head:
Focused suite at the clean base, separate detached worktree:
Broader suite comparison, non-destructive, two separate worktrees at the two commits:
The failing ids match one for one at both commits: six in
tests/test_commit_payloads.py, two intests/test_issue_702_review_findings_f1_f6.py, one intests/test_mcp_server.py::TestPreflightVerification, one intests/test_post_merge_moot_lease.py, one intests/test_reconciler_supersession_close.py. The author's claim that these predate the branch is confirmed and they do not bear on this verdict.Classification probe:
AC4 (marker written for both new forms) is covered by the two new record-tool cases, which pass in the focused run above. This reviewer did not invoke the live record tool, because writing a real contamination marker would fail this session's own review mutation closed.
Verdict rationale
AC1, AC2 and AC4 are met. AC3 is met for the cases the issue enumerates literally. AC5 is not met: the commit-message case it names is covered only in a form that cannot exercise the new code path, and the head regresses that case for the exact string the issue is about. F1 is a correctness defect in the shipped behaviour, so the verdict is request_changes.
[THREAD STATE LEDGER] PR #789 — reviewer verdict request_changes at head
6b58f04What is true now:
6b58f04d396b881d5d242eacac45bcfdfefe2d00; no merge event has occurred; PR #789 remains in open state; issue #787 remains in open state.What changed:
6b58f04d396b881d5d242eacac45bcfdfefe2d00.What is blocked:
&introduces three measured false positives on quoted text containing an ampersand and a kill verb, and the AC5 test for the commit-message case omits the ampersand so it cannot catch them.Who/what acts next:
sleep 1 & pkill -f mcp_server.pystring inside a non-executing command asserting contamination False, correct the test-count arithmetic in the PR body for F2, and push a new head for re-review.Canonical PR State
STATE:
review-request-changes
WHO_IS_NEXT:
author
NEXT_ACTION:
Author implements F1 and the missing AC5 case, corrects the F2 counts, and pushes a new head for re-review.
NEXT_PROMPT:
WHAT_HAPPENED:
An independent reviewer examined PR #789 at head
6b58f04d39in a dedicated detached worktree, read the two-file diff, classified 28 command strings against both the head and a clean-base copy of the guard, and ran the focused suite and five broader modules at both commits. Both forms named in issue #787 are correctly detected and operator precedence is preserved, but splitting on&introduced three measured false positives on quoted text, and the AC5 test that should protect the commit-message case omits the ampersand.WHY:
A false contamination marker fails review, merge, close and completion mutations closed and can only be cleared by a reconciler, so a false positive is an operator-visible stall. The regressing string is the exact example the issue is written around, which makes a documentation or commit-message mention of #787 a trigger.
ISSUE:
787
HEAD_SHA:
6b58f04d39REVIEW_STATUS:
request_changes
MERGE_READY:
no
BLOCKERS:
F1 — quote-unaware splitting on
&yields three measured false positives at this head; the AC5 commit-message test cannot catch them because it omits the ampersand.VALIDATION:
Focused suite 55 passed at head
6b58f04, 47 passed at base35e94e10. Five broader modules give an identical 11-failure signature at both commits (11 failed, 254 passed), confirming those failures predate the branch. Classification of 28 command strings run against head and clean base; both issue forms reproduce as contamination at head and as non-detection at base; three quoted-text regressions measured at head only.LAST_UPDATED_BY:
prgs-reviewer (sysadmin), gitea-reviewer MCP namespace, native transport
Controller Handoff
35e94e107c(startup_head == current_head, stale=false)6b58f04d396b58f04d3935e94e107c6b58f04d3935e94e107c)6b58f04d396b58f04d39&yields three measured false positives at this head, and the AC5 commit-message test omits the ampersand so it cannot catch them6b58f04d39using only the gitea-tools author namespace — implement F1 quote-aware segment splitting, add an AC5 case quoting the fullsleep 1 & pkill -f mcp_server.pystring inside a non-executing command asserting contamination False, correct the F2 test counts in the PR body, re-run the focused suite and the five broader modules at base and at the new head, then hand off to a fresh reviewer. The full paste-able text is in the NEXT_PROMPT block above.fix(guard): split on `&` and unwrap subshells so real daemon kills classify (Closes #787)to fix(guard): quote-aware shell segmentation so only real daemon kills classify (Closes #787)repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #789
issue: #787
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 17962-46269707ca04
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr789-aa4fe1cc
phase: claimed
candidate_head:
aa4fe1cc7btarget_branch: master
target_branch_sha:
35e94e107clast_activity: 2026-07-22T02:07:12Z
expires_at: 2026-07-22T02:17:12Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #789
issue: #787
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 17962-46269707ca04
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr789-aa4fe1cc
phase: validating
candidate_head:
aa4fe1cc7btarget_branch: master
target_branch_sha:
35e94e107clast_activity: 2026-07-22T02:09:13Z
expires_at: 2026-07-22T02:19:13Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #789
issue: #787
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 17962-46269707ca04
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr789-aa4fe1cc
phase: claimed
candidate_head:
aa4fe1cc7btarget_branch: master
target_branch_sha:
35e94e107clast_activity: 2026-07-22T02:29:57Z
expires_at: 2026-07-22T02:39:57Z
blocker: none
Reviewer verdict — APPROVE
PR #789 · Issue #787 · Reviewed head SHA
aa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0Review decision: approve
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin
Workflow-load helper result: workflow_hash: 263d0a6cb8a6, boundary_status: clean, final_report_schema_hash: b6c65affc336
This is an independent re-review by a session that did not author or remediate this pull request. The author handoff and its reported numbers were treated as claims to verify, not as evidence. Every count and every classification below was re-measured in this session.
Prior-landing gate: NOT_LANDED — the pull request is in open state,
merged_atnull,merge_commit_shanull, andgit merge-base --is-ancestor aa4fe1cc prgs/masterreturned non-zero, so headaa4fe1ccis not an ancestor of the target branch.Reviewed head SHA:
aa4fe1cc7bLive head SHA before approval:
aa4fe1cc7b(git ls-remote prgs refs/heads/fix/issue-787-kill-segment-separators)Target branch SHA:
35e94e107c(git ls-remote prgs refs/heads/master)Pushes occurred during validation: no
Prior request-changes state
Review #497 by sysadmin, submitted 2026-07-21T20:19:51-05:00, REQUEST_CHANGES at head
6b58f04d396b881d5d242eacac45bcfdfefe2d00. It is visible,dismissed=false,stale=true, not quarantined. The head advanced toaa4fe1ccafterwards (author_pushed_after_request_changes=true), so the blocker is evaluated against new code rather than reused. No newer substantive review exists:gitea_get_pr_review_feedbackreturns exactly one review,latest_review_state_by_reviewer={"sysadmin": "REQUEST_CHANGES"},approval_visible=false. No conflicting reviewer or merger lease existed at acquisition (gitea_list_workflow_leasescount 0;gitea_assess_pr_sync_statusreportedauthor_lock=false, reviewer_lease=false, merger_lease=false).Scope examined
Diff versus base master
35e94e107c32cfdc4b9ab9a95cb4e94df94077d4: two files, 321 insertions, 4 deletions.runtime_recovery_guard.py—_SEGMENT_SPLIT_REremoved; new_iter_active,_is_redirection,_closes_leading_paren;_strip_subshellrewritten;_split_segmentsrewritten as a scan.tests/test_issue_630_runtime_recovery_guard.py— new cases only, no existing case weakened or deleted.The classifier itself (
_analyse_kill_segment,classify_recovery_command,assess_recovery_command), the contamination model, the gated-task set, the marker lifecycle, and the reconciler-only clearing path are untouched. No widening of detection into other tools. Scope discipline for #787 holds.Disposition of review #497 findings
F1 (BLOCKING at
6b58f04) — RESOLVEDThe splitter is now quote-aware.
_iter_active()walks the string tracking single- and double-quote state and backslash escaping, yielding only positions where a metacharacter is syntactically active;_split_segments()separates only at those positions. This is the preferred remedy #497 named, and it addresses the class rather than the three measured strings.All three measured regressions are gone, and the two pre-existing
;/|false positives #497 documented are retired as well — so the head is strictly better than base on this axis, not merely better than the rejected head. Measured this session at all three commits:Every row where base reads True and the head reads False is a command that executes no kill at all under shell semantics — an escaped or quoted separator makes the text an argument, not syntax — so each is a false positive being retired, never a detection being lost.
AC5 coverage is now real.
F1_QUOTED_COMMANDSholds the full ampersand-bearing strings and is asserted twice: once against the classifier (test_quoted_ampersand_examples_from_review_f1_are_not_kills) and once against the marker-writing tool (test_record_tool_does_not_mark_a_quoted_mention_of_the_kill_string, assertingcontaminated is False,marked is False, and_load_runtime_recovery_marker(...) is None). The second assertion is the one that matters operationally, because the marker is what fails mutations closed. Single-quoted, escaped, and escaped-inside-single-quote forms carry their own cases.F2 (MINOR, evidence accuracy) — RESOLVED
The body now states 47 at base, 55 at the rejected head, 64 at this head, and says the rejected head added 8 cases rather than 9. Re-counted independently this session in three separate detached worktrees; the corrected figures are exact. Full-suite pass counts corroborate the arithmetic: base 4173 passed versus head 4190 passed, a difference of 17, matching 64 − 47.
F3 (INFORMATIONAL) — RESOLVED
_strip_subshellnow removes a trailing)only when_closes_leading_paren()proves it closes a(this call stripped, tracking nesting over active characters.kill $(pgrep -f myapp)is returned intact, and(kill $(pgrep -f myapp))unwraps tokill $(pgrep -f myapp)with the substitution balanced. An unmatched leading(is still dropped alone, which is required because splitting a wrapped compound orphans the opening half._is_redirection()keeps2>&1,>&2and&>logout of separator position.Segmentation properties verified
Verified by direct calls to
_split_segmentsat the pinned head, not inferred from the tests:The last eight rows are cases the diff does not name and the tests do not assert, chosen to test generality rather than the reported examples; each matches shell semantics.
echo "x"&pkill ...andecho a\ & pkill ...are the important ones: quote-awareness does not make a separator inert merely because quoting appears earlier in the line, so the fix suppresses false positives without opening an evasion path.True-positive reproductions at the pinned head
All required forms classify as contamination with
reason_class=manual_daemon_kill(broad sweep asbroad_process_kill), reproduced this session:echo 'a\' & pkill -f mcp_server.pyis the sharp case: POSIX gives a backslash no escaping power inside single quotes, so the quote closes and the&is genuinely active. The head detects it, which shows the quote handling is a faithful shell model rather than a blunt "ignore anything near a quote" rule.No-false-positive cases, unchanged at all three commits
Marker-creation proof
assess_recovery_commandstill has exactly one production consumer:gitea_mcp_server.py:15980, insidegitea_record_daemon_process_kill_attempt. Verified by repository-wide search at the pinned head forassess_recovery_command,classify_recovery_commandandbuild_contamination_recordoutsidetests/. That consumer writes a marker only underif contaminated and mark:(gitea_mcp_server.py:15990), wherecontaminatedrequires classifier contamination and absent operator authorization. That file is not in this diff, so the marker lifecycle is unchanged; the head narrows what reaches the marker rather than changing when a marker is written. The record-tool tests assert marker creation for both newly detected forms and absence of a marker for all three quoted mentions.Test evidence
All commands run by this reviewer with an absolute interpreter path, in the stated working directory. No repository file was edited. All three worktrees are session-owned, detached, and were clean before and after every run.
Focused suite at the pinned head:
Focused suite at the rejected head, separate detached worktree:
Focused suite at the clean base, separate detached worktree:
Author-reported focused counts (47 / 55 / 64) are confirmed exactly.
Full suite at the pinned head:
Classification probe:
Validation failure history
The full suite at the pinned head exited 1 with 11 failing tests. No rerun was performed and no failure was made to disappear; the failures are dispositioned by the pre-merge baseline comparison below rather than by retry.
tests/test_commit_payloads.py— 6 failures (test_commit_files_content_plain,test_commit_files_local_path,test_commit_files_multiple_sources_blocked,test_commit_files_outside_scope_blocked,test_commit_files_traversal_blocked,test_commit_files_workspace_path)tests/test_issue_702_review_findings_f1_f6.py— 2 failures (TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe,TestF2PreflightPathVerification::test_preflight_and_mutation_context_agree_on_dead_binding)tests/test_mcp_server.py::TestPreflightVerification::test_declared_clean_task_worktree_ignores_control_checkout_violation— 1 failuretests/test_post_merge_moot_lease.py::TestAcquireToolRefusesMergedPR::test_acquire_tool_fails_closed_on_merged_pr_without_posting— 1 failuretests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue— 1 failureSuspected cause: host/workspace-state dependent preflight and commit-payload fixtures, unrelated to shell segmentation. Reproduced: yes, deterministically. Evidence that they are not caused by this change: none of the five modules imports or exercises
runtime_recovery_guard, and the identical set fails on the pre-merge base commit with this change absent.Pre-merge baseline proof
The comparison was run as a full suite at both commits, not only on the five affected modules, so the equivalence claim rests on the whole surface. The baseline commit is the pre-merge base of this branch, not post-merge master:
aa4fe1cchas not landed, so35e94e10is master as it stands without this work.Baseline validation worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/baseline-master-pr789
Baseline target SHA:
35e94e107cPre-merge base commit:
35e94e107cTested commit:
aa4fe1cc7bCommand: /opt/homebrew/bin/python3.14 -m pytest -q -s
Exit status: 1 at both commits
Baseline result: 11 failed, 4173 passed, 6 skipped, 1 warning, 493 subtests passed in 81.62s
Tested-commit result: 11 failed, 4190 passed, 6 skipped, 1 warning, 493 subtests passed in 80.60s
Baseline failures: 6 in tests/test_commit_payloads.py, 2 in tests/test_issue_702_review_findings_f1_f6.py, 1 in tests/test_mcp_server.py::TestPreflightVerification, 1 in tests/test_post_merge_moot_lease.py, 1 in tests/test_reconciler_supersession_close.py
Tested-commit failures: the same 11 test ids, one for one
Failure signature: identical 11 test ids at both commits, listed under Validation failure history
Failure signatures match: true
Baseline worktree dirty before validation: false
Baseline worktree dirty after validation: false
Tested-commit worktree dirty before validation: false
Tested-commit worktree dirty after validation: false
The 17-test pass-count difference (4173 to 4190) equals the focused-suite delta (47 to 64), so the head adds tests and removes none. The author's claim that the same 11 failures reproduce on clean base is confirmed, and confirmed on a wider basis than the author measured.
Validation status: baseline-equivalent failure accepted
Mutation-capability table
gitea_acquire_reviewer_pr_lease(PR #789)review_pr/gitea.pr.review, role reviewer17962-46269707ca04, comment 13838gitea_whoamithengitea_resolve_task_capability(task="review_pr")returnedallowed_in_current_session=truebefore the callgitea_heartbeat_reviewer_pr_lease(PR #789)review_pr/gitea.pr.review, role reviewergitea_whoamithengitea_resolve_task_capability(task="review_pr")re-resolved immediately before the call, after a preflight-order rejection of an earlier attemptgitea_mark_final_review_decision(PR #789, approve)review_pr/gitea.pr.review, role revieweraa4fe1ccgitea_whoamithengitea_resolve_task_capability(task="review_pr")re-resolved immediately before the callgitea_acquire_reviewer_pr_lease(PR #789, second acquisition)review_pr/gitea.pr.review, role reviewer17962-46269707ca04, comment 13843, after the 10-minute lease TTL lapsed during validation;gitea_assess_reviewer_pr_leasefirst provedactive_lease: null, so no foreign lease was displacedgitea_whoamithengitea_resolve_task_capability(task="review_pr")re-resolved immediately before the callgitea_submit_pr_review(PR #789, approve)review_pr/gitea.pr.reviewwithgitea.pr.approvein the active profile's allowed operationsaa4fe1ccNo mutation beyond review was performed by this session. Merge capability (
merge_pr/gitea.pr.merge) and branch-delete capability (gitea.branch.delete) are both absent from this profile and neither was attempted.Findings and severity
No blocking findings. No correctness, safety, scope or coverage problem remains. Two informational observations, neither a regression and neither in scope for #787:
echo "oops & pkill -f mcp_server.pyclassifies as no kill. This is documented in the_iter_activedocstring and matches the shell, which rejects such a line as a syntax error rather than running its tail. The classifier's only consumer receives a command string a session voluntarily self-reports, so a syntactically invalid line is not a route by which a real kill escapes an honest report.{ pkill -f mcp_server.py; }andsh -c 'pkill -f mcp_server.py'both classify as no kill. Measured at base and at this head, identical at both, so this predates the branch and is not a regression. Issue #787 names only the subshell wrapper, and widening the tokenizer further belongs with the deferred follow-up that would feed the classifier automatically from other tools.Verdict rationale
Issue #787 acceptance criteria, each checked against the pinned head this session:
sleep 1 & pkill -f mcp_server.pyclassifies as contamination with reason classmanual_daemon_kill.(pkill -f mcp_server.py)classifies as contamination with reason classmanual_daemon_kill.pkill -u mcpuser -f myappstays non-contaminating and unambiguous;kill 31337andkill $(pgrep -f myapp)stay ambiguous and non-contaminating.The remediation is a genuine class fix rather than a patch over the three reported strings: it is expressed as shell syntax rules, it generalizes to cases neither the diff nor its tests name, and it retires two false positives that predate this issue. No true positive was lost anywhere between base and this head. The full-suite failures are proven baseline-equivalent on a whole-suite basis. Findings F1, F2 and F3 are each resolved, and no material regression was found, so the verdict is approve.
[THREAD STATE LEDGER] PR #789 — reviewer verdict approve at head
aa4fe1ccWhat is true now:
aa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0; no merge event has occurred; PR #789 remains in open state; issue #787 remains in open state.What changed:
aa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0.17962-46269707ca04(comments 13838, 13840 and 13843); the re-acquisition followed a lapsed 10-minute TTL on this session's own lease, withactive_lease: nullproven first.branches/. No branch, lock, or repository content was altered.What is blocked:
Who/what acts next:
gitea_adopt_merger_pr_leasepinned toaa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0, re-run merge preflight includinggitea_assess_pr_sync_status, and complete the merge only while the live head still equals the approved head.aa4fe1cc; do not treat review #497 as still blocking; do not re-review or re-approve; do not delete the author worktree or another session's worktrees.Canonical PR State
STATE:
review-approved
WHO_IS_NEXT:
merger
NEXT_ACTION:
Merger adopts the reviewer lease for PR #789 at head
aa4fe1cc7b, re-runs merge preflight, and completes the merge only while the live head equals the approved head.NEXT_PROMPT:
WHAT_HAPPENED:
An independent reviewer that did not author or remediate this work examined it at head
aa4fe1cc7bin a dedicated detached worktree, read the full two-file diff, exercised the segmentation scan directly on 22 syntax cases beyond those the diff names, classified 47 command strings against this head, the rejected head6b58f04and clean base35e94e10, and ran the focused suite at all three commits plus the full suite at this head and at base. The quote-aware splitter resolves review #497 finding F1 for the class rather than the reported examples, removes two false positives that predate the issue, loses no true positive, and finding F3's subshell and redirection defects are corrected; the corrected test counts required by F2 match the head exactly.WHY:
A false contamination marker fails review, merge, close and completion mutations closed and only a reconciler may clear it, so the false-positive class that made review #497 blocking had to be closed as a class before this change could land. It is, and the detection required by issue #787 is intact.
ISSUE:
787
HEAD_SHA:
aa4fe1cc7bREVIEW_STATUS:
approved
MERGE_READY:
yes
BLOCKERS:
none
VALIDATION:
Focused suite 64 passed at head
aa4fe1cc, 55 passed at rejected head6b58f04, 47 passed at base35e94e10, each in its own detached worktree. Full suite 11 failed / 4190 passed / 6 skipped at this head and 11 failed / 4173 passed / 6 skipped at base, with identical failing test ids, so the failures are baseline-equivalent; the 17-test pass-count difference equals the focused-suite delta. Classification of 47 command strings run at all three commits plus 22 direct segmentation cases at the head; all required true positives detected, all required false positives retired.LAST_UPDATED_BY:
prgs-reviewer (sysadmin), gitea-reviewer MCP namespace, native transport
Canonical Handoff
Controller Handoff
aa4fe1cc35e94e107c(startup_head == current_head, stale=false, restart_required=false)aa4fe1cc7baa4fe1cc7b35e94e107caa4fe1ccis not an ancestor of master bygit merge-base --is-ancestor,merge_commit_shanull,merged_atnull, state open6b58f04d39, REQUEST_CHANGES, dismissed=false, stale=true, not quarantined; the head advanced toaa4fe1ccafterwards (author_pushed_after_request_changes=true), so it is superseded rather than overridden; blocker revalidated live in this session at the new headaa4fe1cc7b35e94e107c); comparison worktree /Users/jasonwalker/Development/Gitea-Tools/branches/review-789-rejected-6b58f04 (detached at6b58f04d39)aa4fe1cc, 55 at6b58f04, 47 at 35e94e10; full suite 11 failed / 4190 passed / 6 skipped ataa4fe1ccand 11 failed / 4173 passed / 6 skipped at35e94e10with identical failing ids; 47-string classification probe at all three commits; 22 direct segmentation cases at the head. Commands, cwd, HEAD and interpreter path are quoted under Test evidence.aa4fe1cc7baa4fe1cc, baseline-master-pr789 at35e94e10, review-789-rejected-6b58f04 at 6b58f04; no index or working-tree change in any of them; all three are retained for the merger and no local checkout was deletedaa4fe1cc7baa4fe1cc7b, re-runs merge preflight, and completes the merge only while the live head equals the approved headaa4fe1cc7bover base master35e94e107c. The full paste-able text is in the NEXT_PROMPT block above.aa4fe1cc7b, re-runs merge preflight, and completes the merge only while the live head equals the approved headactive_lease: nullproven first; no lock file touched; no process killed; no daemon restarted; the author namespace was not used for any action; no direct API calls, curl, tea or gh; no branch or repository content modified; the author worktree and the two worktrees owned by the prior reviewer session were inspected for status only and left untouched; the three worktrees created by this session are the only local repository artifactsadopted_at: 2026-07-22T02:39:34Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 17962-46269707ca04
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 13843
adoption_reason: merger-handoff-approved-head
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #789
issue: #787
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 79158-9d38efff1e7c
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-789
phase: adopted
candidate_head:
aa4fe1cc7btarget_branch: master
target_branch_sha:
35e94e107clast_activity: 2026-07-22T02:39:34Z
expires_at: 2026-07-22T02:49:34Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-22T02:40:04.545519+00:00approveon PR fix(guard): quote-aware shell segmentation so only real daemon kills classify (Closes #787) (#789)closed(merged=True)3d0c13fa5a756ff94441dc57cf4e74318f45ba413prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.
[THREAD STATE LEDGER] PR #789 post-merge reconciliation — worktree cleanup complete, source branch retained under an active worktree binding
What is true now:
3d0c13fa5a756ff94441dc57cf4e74318f45ba41, merged_at2026-07-21T21:40:03-05:00, headaa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0. Issue #787 is in closed state and carries nostatus:pr-openlabel.gitea_authorize_reconciliation_cleanup_phase(delete_capability_verified: true). Two retained worktrees removed. Source branchfix/issue-787-kill-segment-separatorsretained ataa4fe1ccon both the remote and locally.gitea_assess_master_parityreportsin_parity: true,stale: false,restart_required: false,startup_head == current_head == 3d0c13fa5a756ff94441dc57cf4e74318f45ba41.gitea_assess_terminal_label_hygieneclean=true across 427 issues, residual_count 0.aa4fe1ccproven an ancestor ofprgs/master.What changed:
branches/merge-pr-789andbranches/review-pr789-aa4fe1cc. Both were clean, detached ataa4fe1cc, and contained in master. Snapshot diff confirmed exactly two removals with zero other entries added or changed;branches/directory count 50 to 48, registered worktrees 43 to 41.3d0c13fa, restoring exact parity. No process was killed.What is blocked:
gitea_cleanup_merged_pr_branchre-run on the restarted runtime returns the same verdict: its own assessment reportssafe_to_delete: true,remote_branch_exists: true, emptyblock_reasons,recommended_action: delete_remote_branch, yet it fail-closes withblocker_kind: active_branch_ownership,blocking_categories: ["worktree_binding"]. The sole obstruction is thatbranches/fix-issue-787-kill-segment-separatorsstill has the target branch checked out. That worktree is classifiedactive_issue_workwithpreserve: Trueby the canonical snapshot because the durable #787 issue lock is live by time:work_lease.created_at 2026-07-22T01:37:57Z,work_lease.expires_at 2026-07-22T05:37:57Z, owner pid 52938 dead. Reconciliation stopped rather than removing a worktree the canonical workflow marks for preservation.Who/what acts next:
2026-07-22T05:37:57Z, when the #787 work lease lapses, or once it is released through its sanctioned owner path, removebranches/fix-issue-787-kill-segment-separatorsand re-rungitea_cleanup_merged_pr_branchfor PR #789, then delete the local branch.branches/issue-681-preserve-wipor any unrelated artifact, and do not kill MCP daemon processes.Canonical Issue State
STATE:
post-merge-reconciliation-partial: lifecycle state terminal and verified, lease state terminal, worktree cleanup complete, source branch retained behind an active worktree binding
WHO_IS_NEXT:
user
NEXT_ACTION:
After the #787 work lease lapses at
2026-07-22T05:37:57Z(or is released through its sanctioned owner path), remove the worktreebranches/fix-issue-787-kill-segment-separators, re-rungitea_cleanup_merged_pr_branchfor PR #789 to delete the remote branchfix/issue-787-kill-segment-separators, verifyverified_absent: true, then delete the local branch of the same name.NEXT_PROMPT:
WHAT_HAPPENED:
Preflight proved identity
sysadmin/prgs-reconciler, role reconciler, and workflowskills/llm-project-workflow/workflows/review-merge-pr.mdhash263d0a6cb8a6with a clean session boundary. PR #789 state, its head, its merge commit and its parents (35e94e107c32plusaa4fe1cc7bd2),prgs/master, the closed state of Issue #787, and ancestry containment were verified live. The comment-backed merger adoption lease from comment 13848 (session79158-9d38efff1e7c, adopted from reviewer session17962-46269707ca04/ comment 13843) reached its TTL at2026-07-22T02:49:34Z, sogitea_cleanup_post_merge_moot_leasereturnedlease_moot: falseandcleanup_allowed: false, reporting that the PR has reached a terminal state and no active reviewer lease remains, so there was nothing to clean and no marker was posted. The control-plane DB holds zero leases for PR #789, and all four PR-789 session pids are dead (author 52938, reviewers 84153 and 17962, merger 79158). The cleanup phase was authorized and the two clean detached worktrees were removed. The source-branch deletion fail-closed on worktree binding, both before and after the runtime restart.WHY:
The removed artifacts belonged only to the completed PR #789 lifecycle, so reclaiming them carried no risk to live work. The author worktree was preserved because the canonical snapshot marks it
preserve: Trueunder a work lease that has not yet lapsed. Its head9e89efcis a sibling of the published head — same parent6b58f04, same tree9b12bc5f, and an empty diff againstprgs/master— so no unpublished work exists there, but proving content containment does not by itself authorize removing an artifact the workflow marks for preservation, and releasing that lease belongs to its owner path rather than to a reconciler. The source branch therefore stays until the binding clears.RELATED_PRS:
PR #789 (merge commit
3d0c13fa5a756ff94441dc57cf4e74318f45ba41, the target of this reconciliation); PR #786 (predecessor that landed #630 at35e94e107c32); PR #788 (unrelated, remains in open state onfeat/issue-610-live-remote-parity, untouched).BLOCKERS:
Source branch
fix/issue-787-kill-segment-separatorsretained on the remote and locally.gitea_cleanup_merged_pr_branchfail-closes withblocker_kind: active_branch_ownershipandblocking_categories: ["worktree_binding"]becausebranches/fix-issue-787-kill-segment-separatorsholds the branch under the #787 issue lock, whosework_leaseruns to2026-07-22T05:37:57Zwith a dead owner pid 52938. No other blocker remains: parity is exact, capability resolves, and the branch assessment itself reportssafe_to_delete: true.VALIDATION:
gitea_assess_master_parity:in_parity: true,stale: false,restart_required: false,startup_head == current_head == 3d0c13fa5a756ff94441dc57cf4e74318f45ba41. PR #789 re-read: still in closed state,merge_commit_sha 3d0c13fa5a756ff94441dc57cf4e74318f45ba41, headaa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0. Issue #787 re-read: still in closed state with nostatus:pr-open.git ls-remote prgs: master3d0c13fa5a756ff94441dc57cf4e74318f45ba41,fix/issue-787-kill-segment-separatorsstillaa4fe1cc7bd2d6f07ea35775a5c5292d94a938e0.git merge-base --is-ancestor aa4fe1cc prgs/masterpasses. Retained worktree: clean,git diff 9e89efc prgs/masterempty, tree9b12bc5f52b57ff15ac12da2009d1a203a3643abidentical to master's tree.branches/merge-pr-789andbranches/review-pr789-aa4fe1ccconfirmed absent;branches/issue-681-preserve-wipconfirmed present and untouched.gitea_assess_terminal_label_hygiene: clean=true, 427 issues checked, residual_count 0. No lease was taken from another session, no lock file edited, no process killed, and no dirty or unpublished content removed.LAST_UPDATED_BY:
sysadmin / prgs-reconciler (post-merge reconciliation cycle for PR #789)