Prevent, attribute, and recover unknown task files written into the control checkout #913
Open
opened 2026-07-25 16:29:29 -05:00 by jcwalker3
·
1 comment
No Branch/Tag Specified
master
docs/issue-930-remote-mcp-coupling-inventory
fix/issue-927-pytest-config
feat/issue-708-mcp-namespace-attachment
fix/issue-892-author-bootstrap-deadlock
fix/issue-686-detect-reject-manual-mcp
feat/issue-707-cross-project-boundaries
fix/issue-690-review-profile-switch-guard
fix/issue-704-prevent-env-workspace-bindings
fix/issue-700-durable-walls
fix/issue-672-mcp-config-drift
fix/issue-689-deterministic-mcp-namespace
fix/issue-678-codex-mcp-reconnect
feat/issue-649-sentry-console-correlation
feat/issue-666-concurrent-mcp-restart-tests
feat/issue-659-maintenance-drain-mode
feat/issue-648-notifications-console
fix/issue-670-direct-master-incident
feat/issue-644-console-recovery
feat/issue-650-providers-insights
feat/issue-669-scoped-component-recovery
docs/issue-668-mcp-ha-rolling-restart
feat/issue-667-console-restart-controls
feat/issue-665-restart-audit
feat/issue-664-break-glass-restart
feat/issue-645-linkage-console
feat/issue-643-request-preview-initiate
fix/issue-897-permission-stale-runtime-classification
feat/issue-641-runtime-session-view
feat/issue-663-restart-classes
feat/issue-661-drain-proof-hard-gate
fix/issue-854-semantic-container-exclusion
issue-640
fix/issue-682-starlette-httpx2
v1.1.0
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
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#913
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.
Problem statement
A conforming client session running the canonical
work-issueworkflow stopped before task selection because the stable control checkout held an untracked task file it did not create and could not attribute. Stopping was correct. What is defective is that the system (a) allowed a supported session to put that file there, (b) reports the control checkout as clean in its own authoritative runtime state while the file is present, (c) has no way to say which session, issue, branch, or worktree owns it, and (d) offers no sanctioned operation that preserves it and restores a clean checkout. The only exit is operator guesswork, and operator cleanup destroyed the evidence before it could be claimed.Supported workflow that was attempted
skills/llm-project-workflow/workflows/work-issue.md14e515cdaa6a7f0ff9f02951c9c9cb1f0f6fcaddf7732bbc263c4df96d16eec976f293ebhashes to the identical value, so this is not a stale-workflow artifact.Chronological reproduction using authoritative state
tests/test_mcp_restart_governance.pyinto the control checkout at/Users/jasonwalker/Development/Gitea-Tools/tests/, size 17,770 bytes, 488 lines. The file was never tracked, never staged, never committed there.branches/feat-issue-666-concurrent-mcp-restart-testsas a different revision: 17,252 bytes, 478 lines, committed as59aab06fe17f80b5d0b87a31c76f761b195d8425and published as PR #910. The control-checkout copy is therefore an earlier, divergent draft, not a duplicate of the committed file — it is unique content with no other copy.work-issueat hash14e515cd…, observed the control checkout onmaster@76f293eb288fa6cbb3134e093fc960ac01517e75with the untracked file present, appliedwork-issue.md§3 (dirty control checkout→ stop immediately) and §4 ("If the main checkout is dirty before selection, stop and produce a recovery handoff"), and stopped before task selection. It preserved the file and performed no Git, repository, issue, branch, commit, push, or PR mutation. This behaviour was correct.master@76f293eb288fa6cbb3134e093fc960ac01517e75,git status --porcelain --untracked-files=allis empty, andtests/test_mcp_restart_governance.pyno longer exists at the control-checkout path. The only surviving copy is the committed one in the #666 worktree, which is a different revision. The 488-line draft was removed with no audit record, no attribution, and no preservation — the recovery failure mode this issue exists to close.First proven divergence from canonical behaviour
The divergence is the write itself, upstream of every gate that later observed it.
Issue #274 (closed) defines the control checkout as read-only for task work, lists "creating files" as a mutation, and states the acceptance criterion: "Any attempted mutation outside
branches/fails before the command runs." In the implementation, that guarantee exists only inside the MCP mutation preflight chain —gitea_mcp_server.py:1537and:1577call_enforce_root_checkout_guard(gitea_mcp_server.py:1872-1889) on the way to a Gitea/Git mutation. A client-side file write into the control checkout never enters that chain, so nothing intercepts it. The first divergence is that a supported session could create the file at all.The second, compounding divergence is that once created, the file is invisible to the enforced guard:
root_checkout_guard.assess_root_checkout_guard(root_checkout_guard.py:52-111) derives dirtiness at line 82 fromreviewer_worktree.parse_dirty_tracked_files.parse_dirty_tracked_files(reviewer_worktree.py:44-62) discards every??line by design, documented as "Untracked entries (??) are ignored."gitea_mcp_server._parse_porcelain_entries(gitea_mcp_server.py:579-590) applies the sameline.startswith("??")exclusion, and it is the source ofdirty_filesingitea_get_runtime_context(gitea_mcp_server.py:688-701) and of_new_tracked_changes_since(:593-601).Consequence: with an untracked task file sitting in the control checkout,
gitea_get_runtime_contextreportsdirty_files: []withdirty_scope: "control checkout", andassess_root_checkout_guardreturnsproven: true. Authoritative runtime state cannot distinguish a genuinely clean control checkout from one holding another session's uncommitted work. This session confirmed the shape: the post-cleanup output is byte-for-byte the same output the tooling would have produced while the file was present.A third inconsistency sits alongside it:
stable_control_runtime.observe_dirty_files(stable_control_runtime.py:600-611) does not filter??, so two dirty definitions coexist in one codebase and can disagree about the same checkout.Correct fail-closed stop vs. the condition that made the stop necessary
work-issue.md§3/§4 is right and must not be relaxed.Additionally, the stop was applied by the model reading the workflow prose against its own
git status— not by a code gate. A safety invariant that #274 required to "fail before the command runs" is currently enforced only by prompt text.No authoritative owner attribution exists
Control-plane ownership is keyed on issues, branches, worktrees, and leases — never on filesystem paths inside the control checkout:
worktree_cleanup_audit.audit_branches_directoryclassifies only entries underbranches/(worktree_cleanup_audit.py:621-640); the control checkout is explicitly excluded as "protected" (:317,:332).agent_temp_artifacts.find_agent_temp_artifacts_from_porcelain(#261) matches untracked repo-root files against three fixed basename globs (_encode_*.py,_emit_*.py,_inline_*.py) and skips anything containing a path separator — sotests/test_mcp_restart_governance.pyis out of scope twice over, and it attributes nothing.dirty_orphan_worktree_recovery(#860) requires a registeredbranches/worktree plus a durable issue lock naming the claimant; neither exists for the control checkout.The only attribution available in this incident was forensic inference from file mtime and content diff against PR #910 — exactly the operator guesswork this issue must eliminate.
No sanctioned recovery path exists
gitea_recover_dirty_orphaned_issue_worktree,gitea_rebind_dirty_same_claimant_author_session, and the #812 preserve/prepare/restore model all require a known issue and a known claimant and operate onbranches/worktrees only. For an unattributable artifact in the control checkout there is no preserve step, no quarantine step, and no clean-checkout verification. Deletion is forbidden bywork-issue.md§4 and by #274; retention blocks every workflow. That is an unrecoverable state, and it silently resolved into data loss.Gate boundary is wrong for issue intake
create-issue.md§3 and §4 make a dirty control checkout blocking for issue creation too, and §18a requiresgitea_create_issueto run from that clean control checkout. So a contaminated control checkout blocks not only author implementation but also the issue-first intake that would file the incident. The intake path is a pure remote mutation with no local tree write; a control checkout holding foreign untracked content is not a safety reason to refuse it, provided intake cannot slide into author work.Model-agnostic impact
Nothing in the failing path is client-specific. The contract is the shared canonical workflow plus the MCP gate layer: the same
work-issue.mdhash, the same_parse_porcelain_entriesfilter, the sameroot_checkout_guard, the same absent attribution and absent recovery. Codex, Claude, Gemini, Grok, and any other conforming client reach the identical stop with the identical lack of a next action. A fix that keys on a client name, a model name, or a User-Agent does not fix this.Relevant source, workflow, and test locations
Source
root_checkout_guard.py:52-111—assess_root_checkout_guard, dirtiness derived at:82reviewer_worktree.py:44-62—parse_dirty_tracked_files,??exclusiongitea_mcp_server.py:555-573—_get_workspace_porcelain(git status --porcelain, no-uall)gitea_mcp_server.py:579-590—_parse_porcelain_entries,??exclusiongitea_mcp_server.py:593-601—_new_tracked_changes_sincegitea_mcp_server.py:688-701—gitea_get_runtime_contextdirty_filesgitea_mcp_server.py:1537,:1577,:1872-1889— guard enforcement sitesstable_control_runtime.py:600-611—observe_dirty_files(does not filter??)worktree_cleanup_audit.py:317,:332,:621-640— control checkout excluded from classificationagent_temp_artifacts.py— repo-root basename-glob detection onlydirty_orphan_worktree_recovery.py,dirty_same_claimant_session_rebind.py—branches/-only recoveryanti_stomp_preflight.py:547— second guard call siteWorkflow
skills/llm-project-workflow/workflows/work-issue.md§3, §4skills/llm-project-workflow/workflows/create-issue.md§3, §4, §18aTests
tests/test_root_checkout_guard.pytests/test_workspace_guard_alignment.pytests/test_stable_control_runtime.pytests/test_create_issue_workspace_guard.pytests/test_issue_618_author_worktree_resolution.pytests/test_anti_stomp_preflight.pytests/test_namespace_workspace_binding.pyVerified architectural boundary responsible
The control checkout /
branches/worktree isolation boundary (#274, #475, #618, #713). It is responsible for prevention (nothing task-related may be created in the control checkout), attribution (every artifact under repository control must trace to an owning session, issue, branch, or worktree), and recovery (unknown work must be preservable without destroying it and without an operator deciding by hand). Today it enforces only a subset of prevention, and only for tracked paths reached through the MCP mutation preflight.Scope
root_checkout_guard,_parse_porcelain_entries, andobserve_dirty_files.Non-goals
branches/worktree recovery (#812, #860, #864) — consume those primitives, do not restate them..gitignoreor local-exclude entries to hide artifacts from the guard.Acceptance criteria
AC1 — One authoritative dirty definition. A single classifier decides control-checkout cleanliness, covers modified, staged, deleted, renamed, and untracked paths, and is used by
assess_root_checkout_guard,gitea_get_runtime_context, andstable_control_runtime. Any two of these reporting different cleanliness for the same checkout is itself a test failure.AC2 — Untracked contamination is visible. With an untracked non-ignored file present in the control checkout,
gitea_get_runtime_contextreports it indirty_fileswithdirty_scope: "control checkout", and the root-checkout guard reportsproven: falsewith that path named. Ignored files are excluded and the exclusion is reported, not silent.AC3 — Prevention or immediate rejection. A task-related write to the control checkout is prevented, or is detected and rejected at the next gate boundary with the exact path named — before any task selection, claim, lease, branch, or commit occurs. The rejection is produced by a code gate, not by workflow prose alone.
AC4 — Provenance is recorded, not inferred. Artifacts created under repository control carry a durable provenance record naming the session identity, profile, role kind, issue, branch, and bound worktree. A read-only query maps an unexpected control-checkout path to its owner, or returns an explicit
owner_unknownverdict with the evidence it consulted. Inference from mtime or content similarity is never presented as attribution.AC5 — Preservation without data loss. A sanctioned MCP operation preserves unknown control-checkout content — tracked modifications and untracked files — byte-for-byte into a durable addressable checkpoint outside the working tree, with per-file content hashes, before anything is moved or removed. The operation is non-destructive and idempotent on unchanged content. It reuses the #812 preserve/restore primitives where they exist rather than duplicating them.
AC6 — Deterministic sanctioned recovery. A single sanctioned operation carries the control checkout from contaminated to verified-clean: classify → attribute (or record
owner_unknown) → preserve → restore the checkout → verify. It has a read-only assessor whose verdict and evidence match the mutating path, and a dry-run that mutates nothing. It never deletes, stashes, resets, cleans, or silently relocates unknown work, and it fails closed on any content-loss risk, ambiguous ownership, or a live competing owner.AC7 — Clean-checkout verification after recovery. After recovery the control checkout passes the canonical clean-checkout preflight under the AC1 definition, no tracked file and no Git ref changed, no unrelated content was removed, and a durable audit record names the checkpoint id, per-file hashes, acting identity and profile, and the disposition of every path.
AC8 — Issue intake stays reachable. With the control checkout contaminated,
gitea_create_issueremains available (it performs no local tree write), whilework_issue,lock_issue,commit_files,create_pr, and every other author implementation mutation stay fail-closed. Intake cannot silently become author work: no assignment, no issue lock, no lease, no branch, and no worktree is created by the intake path.AC9 — Actionable blocked results. Any blocked result from this class names: the exact failed gate; the exact conflicting artifact path(s); the known owner and owning task, or an explicit statement that the owner is unknown; whether recovery is automatic or requires an operator; and the exact sanctioned next action, satisfiable at the phase it is emitted in.
AC10 — Restart honesty. No message in this path reports an MCP restart or reconnect as necessary unless authoritative runtime state (master parity / namespace health) actually requires it.
AC11 — No regression in guard strictness. Dirty, drifted, detached, and non-canonical control checkouts still fail closed for every caller that has not gone through the sanctioned recovery operation. Recovery is an explicit operation, never a general exemption or a bypass flag.
Regression requirements
branches/worktree, both complete normally; neither can claim, quarantine, or destroy the other's work, and neither is blocked by the other's worktree state.owner_unknownverdict with consulted evidence when it does not.gitea_create_issuesucceeds against a contaminated control checkout whilework_issue/lock_issue/commit_files/create_prfail closed, and the intake path creates no assignment, lock, lease, branch, or worktree.masterat live master proceeds through allocation, claim, worktree creation, implementation, and PR creation with no new gate, no new prompt, and no measurable added latency.actual_role; the #618 author worktree resolution; the #713 registered-worktree proof; the #749 / §18a create-issue bootstrap exemption and its non-generalization; and the #860 / #864 dirty-worktree recovery and rebind semantics.Prohibited
tests/test_mcp_restart_governance.py, on issue #666, on PR #910, or on thetests/prefix.git stash,git clean,git reset,git checkout --, or moving files to a temp path without a durable audit record and verified hashes..gitignore/ local-exclude entries that hide artifacts from the guard, or a bypass flag that makes contamination non-blocking.Duplicate search
Searched open (96 issues, limit 400, complete) and closed (407 issues, limit 500, complete) issues, and open PRs (10,
is_final_page: true,inventory_complete: true,total_count: 10). Terms: the proposed title; control checkout, main checkout, untracked, dirty, contamination, orphan, provenance, ownership, worktree, branches-only, recovery, preserve, artifact, intake, issue-first, task selection, allocation; plustests/test_mcp_restart_governance.py,#666,#274.branches/"fails before the command runs"tests/test_mcp_restart_governance.py(PR #910)_parse_porcelain_entriesignores??, sogitea_get_runtime_contextpreflight misses untracked filesgitea_mcp_server.py:583still excludes??). It has no acceptance criteria, does not coverroot_checkout_guard/parse_dirty_tracked_filesorobserve_dirty_files, and covers none of prevention, attribution, preservation, recovery, or intake. Tightening detection alone would not have prevented this incident — detection is the part that (via prose) already stopped the run. This issue must consume #697's fix rather than restate it..mcp_session_*) written into the control checkout; inventory, migration, quarantine of that state classbranches/author worktree with a known issue and known claimant (entry points A and B)issue=Nand derives ownership from a recorded claimant. Unknown-owner artifacts in the control checkout are outside its entire evidence model.rm -f)NOT a duplicateverdict against #697 for the under-detection direction. Its ACs explicitly preserve protection for genuinely dirty control checkouts rather than defining it.recover_author_ownershipbranches/worktree plus a durable lock naming the claimant. Neither exists for the control checkout, and the owner here is unknown by construction._encode_*.py,_emit_*.py,_inline_*.py)tests/test_mcp_restart_governance.pyis out of scope twice over.Verdict: no open or closed issue covers the verified root cause and full acceptance boundary. Coverage is fragmented across prevention (#274, regressed), detection (#697, partial and unfixed), a different residue class (#716), a known-owner recovery primitive (#812/#860), shell-mutation auditing (#867), and a one-off instance cleanup (#681). No new PR addresses it.
Related
branches/feat-issue-666-concurrent-mcp-restart-tests.Canonical issue state
Current reproduction: ignored control-checkout artifact is silently reported clean
A second concrete reproduction now exercises AC1/AC2 with a different artifact class:
/Users/jasonwalker/Development/Gitea-Tools/AGENTS.mdstill exists (177 bytes).git check-ignore -v AGENTS.mdreports.git/info/exclude:29:AGENTS.md.git status --untracked-files=allreports a clean branch, whilegit status --ignored --short --untracked-files=all AGENTS.mdreports!! AGENTS.md.gitea_get_runtime_context(remote="prgs")simultaneously reportsdirty_files: [],workspace_healthy: true,preflight_ready: true, and master parity at26f54851d1d20eff92c339e778125f31c09fa95f.This session did not add the exclude entry and cannot attribute who did. The state changed externally during read-only investigation: earlier Git status exposed
AGENTS.mdas untracked; later it became hidden by.git/info/excludewhile remaining on disk.This directly strengthens AC2: ignored-file exclusion is currently silent in authoritative runtime output. It also supplies a current AC4 attribution case: owner is explicitly unknown, and neither runtime output nor Git metadata records the responsible session/task.
The comment is the only durable mutation from this investigation.
Canonical Issue State
STATE: ready-for-author
WHO_IS_NEXT: author
NEXT_ACTION: Implement #913 AC1-AC11 and regression requirements from a verified clean registered issue worktree, including explicit reporting of ignored exclusions.
NEXT_PROMPT:
WHAT_HAPPENED: Reproduced silent ignored-artifact invisibility using AGENTS.md and recorded exact Git/runtime evidence.
WHY: The current runtime reports a checkout containing an ignored artifact as clean and provides no provenance for the exclusion or file.
RELATED_PRS: none
BLOCKERS: none for an independent author session using a verified clean registered issue worktree.
VALIDATION: git check-ignore identified .git/info/exclude:29; git status --ignored reported !! AGENTS.md; gitea_get_runtime_context reported dirty_files [], workspace_healthy true, preflight_ready true, and master parity true.
LAST_UPDATED_BY: sysadmin (prgs-controller)
Canonical Handoff