feat: add Sentry-to-Gitea incident bridge for MCP workflow failures (Closes #607) #767
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#767
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 #607
Summary
Adds a sanctioned Sentry to Gitea incident bridge that turns recurring MCP runtime failures, workflow collisions, stale leases, and terminal locks into durable Gitea issues and recurrence comments. Gitea stays the workflow source of truth; Sentry is observability input only.
New module
sentry_incident_bridge.pyprovides config loading, token resolution, paginated Sentry reads, sanitization, and watchdog policy. Five MCP tools are registered ingitea_mcp_server.py:gitea_sentry_list_issues— paginated unresolved-issue reads with query/severity/window filtersgitea_sentry_get_issue_events— sanitized recent events for one Sentry issuegitea_sentry_reconcile_issue— decides create / update / ignore, deduplicated by Sentry issue idgitea_sentry_link_gitea_issue— upserts the durableincident_linksmapping only; never creates or reopens a Gitea issuegitea_sentry_watchdog— scan job, dry-run by default,apply=truerequired to writeDurable mapping is delegated to the existing
incident_bridge/gitea_observability_link_issuesubstrate rather than introducing a second workflow authority, so the mapping survives process restarts. Redaction routes strings throughsentry_observability.sanitize_pathto coarse path categories, and Sentry writes fail closed on redaction failure while non-bridge MCP tools fail open on Sentry outages.Linked issue
Review remediation history
Two REQUEST_CHANGES verdicts were recorded against earlier heads.
Review 479 (at
cb6ae0ca) and review 481 (at716fc21a) raised:observation_from_issuenever populates a fingerprint.All three are remediated at the current head:
incident_bridge.incident_recurred()andbuild_recurrence_comment_body()are added, and acomment_issue_fnhook posts a sanitized recurrence comment on theOUTCOME_UPDATEDpath only when the provider reports genuinely new events (event_countorlast_seenadvanced against the pre-upsert link row). The durableincident_linksrow is written before the comment, so a comment failure degrades to "link updated, comment withheld" and never rolls back or blocks the mapping._incident_recurrence_comment_fn()ingitea_mcp_server.pyroutes through the sanctionedgitea_create_issue_commentpath and returnsNoneon dry runs and when the active profile lacksgitea.issue.comment, so the AC8 dry-run default and disabled-mode safety hold.build_recurrence_comment_body()reuses the sameredact_textpath asbuild_gitea_issue_body, so redaction is not re-implemented.gitea_sentry_reconcile_issueandgitea_sentry_watchdogdocstrings and this body now state the real dedupe basis: provider identity (provider, base URL, org, project, Sentry issue id). Fingerprint is no longer claimed.Five new AC4 regression tests were added (43 test defs, against 38 at the previously published head).
Files changed
Against master
edaeede250f46f361784061f025ab0e867172d2c:sentry_incident_bridge.py(new module;comment_issue_fnthreaded throughwatchdog, per-issuerecurrence_commentrecorded in scan results)incident_bridge.py(incident_recurred,build_recurrence_comment_body,CommentIssueFn, recurrence-comment path onOUTCOME_UPDATED)gitea_mcp_server.py(five@mcp.tool()registrations plus_incident_recurrence_comment_fn)tests/test_sentry_incident_bridge.py(new, 43 tests)docs/observability/sentry-integration.mdEvery changed file is in scope for this issue. No generated artifacts, caches, credentials, temporary files, or provenance markers are present.
Branch and head
feat/issue-607-sentry-incident-bridgebc968dd2e05a10529b08da2dea511d61a2404409masteratedaeede250f46f361784061f025ab0e867172d2c(0 commits behind)/Users/jasonwalker/Development/Gitea-Tools/branches/issue-607-sentry-incident-bridge(clean before and after validation)The remediation was integrated locally as merge commit
f0274bc4c08e0a7eeceeec31375b2b6179043755(parents: remediationa487f9fdc94388252b9a234976ca0c9c9ea7f706and published head716fc21a0dd6521d71b6335f362f658654b5f69e), then published throughgitea_commit_filesas server-side commitbc968dd2onto the published head. No force-push and no history rewrite:716fc21aremains an ancestor of the current head.Validation
All commands run in
/Users/jasonwalker/Development/Gitea-Tools/branches/issue-607-sentry-incident-bridge, runner/opt/homebrew/bin/python3, pytest 9.0.3./opt/homebrew/bin/python3 -m pytest tests/test_sentry_incident_bridge.py -q -s-> 43 passed, exit 0./opt/homebrew/bin/python3 -m pytest -q -s-> 2 failed, 3780 passed, 6 skipped, 493 subtests passed in 56.71s.git diff --check-> clean.Baseline comparison for the two full-suite failures
/Users/jasonwalker/Development/Gitea-Tools/branches/baseline-master-607-remediationedaeede250f46f361784061f025ab0e867172d2c(pristine current master, detached)f0274bc4c08e0a7eeceeec31375b2b6179043755/opt/homebrew/bin/python3 -m pytest tests/test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe tests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue -q -stest_reconciler_supersession_closefails on both withAssertionError: expected call not found. Expected: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr') / Actual: verify_preflight_purity('prgs', task='reconcile_close_superseded_pr', org='Scaled-Tech-Consulting', repo='Gitea-Tools');test_issue_702_review_findings_f1_f6fails on both.These two failures are the known pre-existing master drift from #737 (the workspace-repo forwarding change added
org/repokwargs that the older tests still assert against). They lie outside this PR's diff and were deliberately not repaired here — repairing them belongs to a separate #737-scoped task.Acceptance criteria
OUTCOME_UPDATEDpath, gated on genuinely new events, dry-run silent, and covered by five regression tests.incident_linksstore.apply=trueis required to write, and dry runs never comment.Risk
Low. All new surface is opt-in behind
MCP_SENTRY_ISSUE_BRIDGE_ENABLED/MCP_SENTRY_ENABLEDand defaults to dry-run; with the bridge disabled or the token absent, no Sentry calls and no Gitea writes occur. No token, DSN, org slug, or project slug is hardcoded. No existing tool behavior is modified. The recurrence comment cannot corrupt the durable mapping: the link row is written first and a comment failure is recorded, not propagated.Known limitations
Workflow evidence
skills/llm-project-workflow/SKILL.md; workflow:skills/llm-project-workflow/workflows/work-issue.md(task_mode: work-issue, canonical).gitea-tools, profileprgs-author, role author, identityjcwalker3, remoteprgs.startup_head == current_head == local master == live prgs/master == edaeede250f46f361784061f025ab0e867172d2c,in_parity=true,restart_required=false,stop_required=false.gitea_lock_issue(the #768/#769 strict-descendant route):dead_session_recovery.recovered=true,head_relation=strict_descendant, prior pid 44188 dead, replacement pid 94289, recorded head716fc21a, accepted headf0274bc4.adoption_decision=ADOPT,no_existing_pr_proof=true,no_competing_live_lock_proof=true, competing-branch check clear.gitea_commit_fileswith all four payloads sourced fromworkspace_pathin the bound author worktree. Read-after-write verified: PR head moved716fc21a->bc968dd2;incident_bridge.py,sentry_incident_bridge.py, andtests/test_sentry_incident_bridge.pyconfirmed byte-identical to the verified local worktree by git blob SHA (df41ad9bd957a92cfda83801a468202190b2412d,56bab8ea4ed77564f2ac7f5c5c6b83c64e3a0460,8f19510b61d2fda4baf1e60ed48ca30a908d5e96).masteratedaeede250f4, clean, and was not used for task work.Author role only. This PR must be reviewed and merged by an independent reviewer and merger.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 48518-ac60c7148b3c
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/issue-607-sentry-incident-bridge
phase: claimed
candidate_head:
cb6ae0ca50target_branch: master
target_branch_sha:
d12adabeb1last_activity: 2026-07-20T05:44:00Z
expires_at: 2026-07-20T05:54:00Z
blocker: none
Canonical PR State
STATE:
PR #767 reviewed at pinned head
cb6ae0ca50by an independent reviewer; one native REQUEST_CHANGES verdict recorded for a missing acceptance criterion.WHO_IS_NEXT:
author
NEXT_ACTION:
Author implements the AC4 recurrence-comment path for issue #607 with regression tests, corrects the fingerprint dedupe claim, pushes a new head, and hands off to a fresh independent reviewer.
NEXT_PROMPT:
WHAT_HAPPENED:
An independent prgs-reviewer session acquired a reviewer PR lease on #767, pinned head
cb6ae0ca, read issue #607 and its full discussion including author handoff comment 13094, inspected the complete diff of four files, traced the delegation into the existing incident_bridge and control_plane_db substrate, and independently reproduced the recorded test evidence. Nine of the ten acceptance criteria in issue #607 were confirmed met. AC4 was found unimplemented while the PR body asserts it is satisfied, so a REQUEST_CHANGES verdict was recorded instead of an approval.WHY:
AC4 is a numbered acceptance criterion of issue #607 and the PR closes that issue. No code in this diff, and none in the substrate it delegates to, posts a comment on the linked Gitea issue. A recurring incident therefore creates one issue that then goes permanently silent, since event_count and last_seen advance only inside the control-plane DB and are invisible to anyone reading the issue. The PR body listing AC4 as met makes the gap a correctness-of-record problem as well as a functional one, so it must be resolved before this work lands.
ISSUE:
#607
HEAD_SHA:
cb6ae0ca50REVIEW_STATUS:
REQUEST_CHANGES
MERGE_READY:
no
BLOCKERS:
Blocker classification: code-defect blocker. F1: issue #607 AC4 recurrence comments are not implemented. F2 is a non-blocking evidence correction regarding an unsupported fingerprint dedupe claim.
VALIDATION:
Focused suite tests/test_sentry_incident_bridge.py: 38 passed. Full branch suite: 3749 passed, 6 skipped, 2 failed. The identical two node ids fail on pristine master
d12adabeb1with identical signatures, so both are pre-existing #737 drift, are not attributed to PR #767, and were deliberately not repaired in this review. Control checkout verified clean on master atd12adabe. Runtime parity verified: startup_head == current_head == local master == live prgs/master ==d12adabe. Reviewer identity sysadmin differs from PR author jcwalker3.LAST_UPDATED_BY:
sysadmin / prgs-reviewer / independent review cycle / 2026-07-20
Verdict: REQUEST_CHANGES
Implementation quality is high and most of issue #607 is met cleanly. One numbered acceptance criterion is not implemented, and the PR body asserts that it is.
F1 (blocking) — AC4 recurrence comments are never posted
Issue #607 AC4 requires: "Existing linked Gitea issues are updated with recurrence comments when Sentry events continue." The PR body lists this as satisfied ("4. Continued events post recurrence comments on the linked issue."). No code in this diff, or in the substrate it delegates to, posts an issue comment.
Evidence:
Consequence: when a recurring MCP failure keeps firing, the durable Gitea issue is created once and then goes permanently silent. That is precisely the recurrence signal AC4 exists to surface, and it is also what issue #607 means by "update an existing issue" in its gitea_sentry_reconcile_issue specification.
Requested change: post a recurrence comment on the linked Gitea issue on the OUTCOME_UPDATED path, through the sanctioned gitea_create_issue_comment route that issue #607 names, with the same redaction applied to the body and the same dry-run default. Cover it with a test asserting a comment is posted on the second scan and that a dry-run scan posts none. Alternatively, if the intent is to defer AC4, state that explicitly and correct the PR body rather than listing AC4 as met.
F2 (non-blocking, evidence correction) — fingerprint dedupe is claimed but never populated
The PR body states reconciliation is "deduplicated by Sentry issue id and fingerprint", and the gitea_sentry_reconcile_issue docstring repeats the claim. observation_from_issue (sentry_incident_bridge.py lines 516-554) never sets a fingerprint key, so normalize_incident resolves inc.fingerprint to None for every observation this bridge produces, and the fingerprint-conflict branch in incident_bridge._link_conflict is unreachable from this path.
This is not a correctness fault: dedupe by provider, base URL, org, project, and issue id works, and the dedupe tests genuinely pass. Only the stated basis is wrong. Either populate fingerprint from the Sentry payload or drop fingerprint from the claim. docs/observability/sentry-integration.md is already accurate on this point and describes dedupe by issue id alone.
What was verified and found correct
[THREAD STATE LEDGER] PR #767 / Issue #607 — REQUEST_CHANGES recorded at
cb6ae0caWhat is true now:
cb6ae0ca50, unchanged from the pinned headd12adabeWhat changed:
What is blocked:
Who/what acts next:
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 44257-b557247ab1ee
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767
phase: claimed
candidate_head:
716fc21a0dtarget_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T08:33:14Z
expires_at: 2026-07-20T08:43:14Z
blocker: none
Canonical PR State
STATE:
PR #767 re-reviewed at pinned head
716fc21a0dby a fresh independent reviewer. The head advanced fromcb6ae0caby a master merge only. It carries no remediation of review 479, so a second native REQUEST_CHANGES verdict is recorded at this head.WHO_IS_NEXT:
author
NEXT_ACTION:
Author publishes the existing local remediation commit a487f9fdc94388252b9a234976ca0c9c9ea7f706 (or an equivalent) to branch feat/issue-607-sentry-incident-bridge so that PR #767 carries the AC4 recurrence-comment implementation and the F2 evidence correction, then hands off to a fresh independent reviewer.
NEXT_PROMPT:
WHAT_HAPPENED:
A fresh independent prgs-reviewer session acquired a reviewer PR lease on #767 against a clean detached reviewer worktree at the pinned head, verified reviewer independence from the PR author, and inspected the complete diff. The head was found to have advanced from the previously reviewed
cb6ae0cato716fc21a. Ancestry analysis showed716fc21ais a two-parent merge ofcb6ae0caand masteredaeede, and that the remediation commit a487f9f is not an ancestor of it. Direct source inspection at the pinned head found no comment-posting, recurrence, or fingerprint code in sentry_incident_bridge.py. The focused suite returns 38 tests at this head against the 43 recorded for the remediation. Both review 479 findings therefore persist unchanged on the published PR.WHY:
Gitea reports author_pushed_after_request_changes=true for this PR, which invites a downstream reader to treat review 479 as resolved by the new head. It is not. The published head contains a master merge and nothing else, so the AC4 gap that blocked this PR at
cb6ae0cais still present at716fc21a, and the PR body still lists AC4 as met and still claims fingerprint-based dedupe. Recording a second verdict at the current head keeps the blocking state attached to the head a merger would actually act on, rather than leaving it on a head Gitea now marks stale.ISSUE:
#607
HEAD_SHA:
716fc21a0dREVIEW_STATUS:
REQUEST_CHANGES
MERGE_READY:
no
BLOCKERS:
Blocker classification: code-defect blocker. F1 (carried forward from review 479, still unremediated at this head): issue #607 AC4 recurrence comments are not implemented. F2 (carried forward, non-blocking evidence correction): the fingerprint dedupe claim in the PR body and in the gitea_sentry_reconcile_issue docstring remains unsupported. F3 (new, record-integrity): the PR body still documents head
cb6ae0caand based12adaband was not updated when the head advanced.VALIDATION:
All commands run from a clean detached reviewer worktree /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767 at
716fc21a, runner /opt/homebrew/bin/python3, pytest 9.0.3. Focused tests/test_sentry_incident_bridge.py -q -s: 38 passed. Full suite -q -s: 3775 passed, 6 skipped, 2 failed, 493 subtests passed, 58.76s. The two failures are tests/test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe and tests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue. Both were independently reproduced in this session on a pristine baseline worktree at current masteredaeede250with identical signatures, so both are pre-existing #737 drift, are not attributed to PR #767, and were not repaired here. Note this baseline is current masteredaeede, not the olderd12adabthe PR body cites. Diff scope confirmed against masteredaeede: 4 files, 403 additive lines in gitea_mcp_server.py with no modification of existing server code. Reviewer identity sysadmin differs from PR author jcwalker3. Runtime parity verified in_parity=true, restart_required=false, startup_head == current_head == local master ==edaeede.NATIVE_REVIEW_PROOF:
Native MCP transport, production mode, entrypoint mcp_server, pid 44257, profile prgs-reviewer, identity sysadmin, workflow skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6, final report schema hash a7634e7b8689. Reviewer lease session 44257-b557247ab1ee, comment 13161. Verdict submitted through gitea_mark_final_review_decision then gitea_submit_pr_review with expected_head_sha pinned to
716fc21a.LAST_UPDATED_BY:
sysadmin / prgs-reviewer / independent review cycle / 2026-07-20
Verdict: REQUEST_CHANGES
The implementation remains high quality, and the nine acceptance criteria confirmed met by review 479 are still met. This verdict is recorded because the published head does not contain the remediation that review 479 required, while the surrounding signals suggest that it does.
F1 (blocking, carried forward) — AC4 recurrence comments are still not implemented at this head
Review 479 recorded REQUEST_CHANGES at
cb6ae0cabecause issue #607 AC4 ("Existing linked Gitea issues are updated with recurrence comments when Sentry events continue") is unimplemented while the PR body lists it as satisfied. That finding is unchanged at716fc21a.Evidence gathered at the pinned head:
git rev-list --parents -n 1 716fc21agives parentscb6ae0caandedaeede. The head advance is a merge of master into the branch.git merge-base --is-ancestor a487f9f 716fc21areturns false, so the remediation commit is not contained in the published head.cb6ae0caand716fc21aby blob hash — sentry_incident_bridge.py, tests/test_sentry_incident_bridge.py, and docs/observability/sentry-integration.md.git diff cb6ae0ca 716fc21aover those paths is empty. The only delta in gitea_mcp_server.py between the two heads is the #769 strict-descendant recovery change inherited from master.716fc21a:git show 716fc21a:sentry_incident_bridge.pymatched againstcomment,recurrence, andfingerprintreturns no hits at all. There is no comment-posting call, and no fingerprint key, anywhere in the module.716fc21a: 38 test defs in tests/test_sentry_incident_bridge.py, and the focused run gives 38 passed. The author's own record for the remediation cites 43. The five AC4 cases are absent.Consequence is unchanged from review 479: a recurring MCP failure creates one durable Gitea issue that then goes permanently silent, because event_count and last_seen advance only inside the control-plane DB and never reach anyone reading the issue.
The requested change is unchanged, and the work already exists. Local commit a487f9f in branches/issue-607-sentry-incident-bridge implements precisely what review 479 asked for, and its approach is sound on each point this review was asked to check: the comment fires only on OUTCOME_UPDATED and only when incident_recurred() sees a genuine event_count or last_seen advance, so an unchanged rescan stays silent and the exactly-once property holds; build_recurrence_comment_body() reuses the same redact_text path as build_gitea_issue_body, so redaction is not re-implemented; the durable incident_links row is written before the comment, so a comment failure degrades to "link updated, comment withheld" without corrupting the mapping or causing a duplicate issue; and _incident_recurrence_comment_fn() returns None on dry runs and when the profile lacks gitea.issue.comment, so the AC8 dry-run default and disabled-mode safety hold. That commit is unpushed. Publishing it is the remaining work, not re-implementing it.
F2 (non-blocking, carried forward) — fingerprint dedupe claim still unsupported at this head
observation_from_issue still never sets a fingerprint key at
716fc21a, so inc.fingerprint is None for every observation this bridge produces and the fingerprint-conflict branch in incident_bridge._link_conflict remains unreachable from this path. Dedupe by provider, base URL, org, project, and issue id works correctly and its tests genuinely pass; only the stated basis is wrong. The PR body and the gitea_sentry_reconcile_issue docstring both still claim dedupe "by Sentry issue id and fingerprint". a487f9f corrects this claim. docs/observability/sentry-integration.md was already accurate.F3 (new, non-blocking, record integrity) — the PR body no longer describes the PR
The PR body still states "Head SHA: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b" and "Base: master at d12adabeb1cf7810b1286ca8871a9b680646021c", and reports validation counts (3749 passed) taken at that head against that base. The live head is
716fc21aand live master isedaeede. It also still lists AC4 as met, which F1 shows it is not. When the remediation is published, update the body to the new head, the new base, the re-run counts, and an accurate AC4 statement.Integration with the recovery changes now on master — verified clean
The merge of master
edaeedeinto this branch was checked specifically, since the review asked about integration with the #768/#769 recovery work now present on master:edaeedethe branch is purely additive: 4 files, +403 lines in gitea_mcp_server.py appended after gitea_observability_link_issue, with no modification or deletion of existing server code. The bridge cannot regress the recovery path by construction.edaeedein this session. The merge introduced no new failure.Re-verified and still correct at this head
Capability boundaries on all five tools fail closed and gate gitea.read at entry, with the watchdog additionally requiring create_issue permission before apply. Dry-run is the default on all three writing tools and apply=true is refused unless MCP_SENTRY_ISSUE_BRIDGE_ENABLED is set. incident_links on the #613 control-plane DB remains the sole durable mapping authority, with no second linking substrate introduced. Redaction is sound: sanitize_path is applied to path-shaped substrings rather than whole free-text fields, credential-bearing permalinks are dropped, sensitive tag keys are removed, and tests prove no token reaches tool output. Failure handling maps 401/403 to missing_token and 5xx to sentry_unavailable, fails closed before any HTTP call when the token or org/project is absent, and isolates a single bad issue so it cannot abort a scan. Pagination follows the Link header under a max_pages cap and reports inventory_complete honestly. AC6 restart persistence and AC7 non-reopen remain proven by their tests. No duplicate issue creation path was found. The diff stays inside issue #607 with no generated artifacts, caches, or credentials.
[THREAD STATE LEDGER] PR #767 / Issue #607 — second REQUEST_CHANGES recorded at
716fc21aWhat is true now:
716fc21a0d, matching the pinned head, re-verified immediately before submissioncb6ae0caby a merge of masteredaeedeand nothing else; remediation commit a487f9f is not an ancestor of the published headcb6ae0ca, undismissed and marked stale by Gitea; this review adds a second REQUEST_CHANGES at the current headedaeedeWhat changed:
716fc21aand branches/baseline-master-767-review atedaeede. No file edits, no commits, no pushes, no branch or worktree deletion. The control checkout stayed on master atedaeedeand clean. The author worktree branches/issue-607-sentry-incident-bridge and both retained baseline worktrees were not modified.What is blocked:
716fc21aWho/what acts next:
716fc21ausing the #769 strict-descendant recovery route, correct the PR body per F2 and F3, re-run focused and full validation at the new head, and hand off to a fresh independent reviewer716fc21a, publishing a487f9f firstrepo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 44257-b557247ab1ee
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767
phase: released
candidate_head:
716fc21a0dtarget_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T08:38:19Z
expires_at: 2026-07-20T08:48:19Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 21628-f893fba2f0c4
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767-bc968dd2
phase: claimed
candidate_head:
bc968dd2e0target_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T09:22:03Z
expires_at: 2026-07-20T09:32:03Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 21628-f893fba2f0c4
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767-bc968dd2
phase: validating
candidate_head:
bc968dd2e0target_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T09:24:01Z
expires_at: 2026-07-20T09:34:01Z
blocker: none
Canonical PR State
STATE:
PR #767 reviewed at pinned head
bc968dd2e0by a fresh independent reviewer. Review 481 findings F1, F2, and F3 are independently confirmed remediated at this head. One native APPROVE verdict is recorded atbc968dd2.WHO_IS_NEXT:
merger
NEXT_ACTION:
An independent prgs-merger acquires a merger PR lease on #767, re-verifies the visible undismissed APPROVE at head
bc968dd2e0, reassesses checks, commits behind, conflicts, and mergeability, and merges through the sanctioned MCP merge operation.NEXT_PROMPT:
WHAT_HAPPENED:
A fresh independent prgs-reviewer session acquired a reviewer PR lease on #767 against a newly created clean detached worktree at the pinned head, verified reviewer identity sysadmin differs from PR author jcwalker3, confirmed the author execution had ended and durable handoff comment 13199 was present on issue #607, and independently re-evaluated review 481 findings F1, F2, and F3 at head
bc968dd2rather than relying on the author record. The published tree was compared blob by blob against the author's verified local integrated commit f0274bc4, which independently closes the one gap the author could not verify. The complete diff of all five changed paths was inspected, the recurrence code path was traced end to end through incident_bridge.reconcile_incident, sentry_incident_bridge.watchdog, and gitea_mcp_server._incident_recurrence_comment_fn, all five AC4 regression tests were read for substance rather than counted, and focused, shared-substrate, full-suite, and pristine-master baseline runs were executed. All three findings are remediated and no new defect was found, so one native APPROVE verdict was recorded.WHY:
Review 481 recorded three findings against head
716fc21a. Each is now independently disproven as a live defect atbc968dd2. F1: the recurrence path exists and behaves as AC4 requires — the comment fires only on OUTCOME_UPDATED and only when incident_recurred() observes a genuine advance measured against the pre-upsert link row, the durable incident_links write at incident_bridge.py:788 precedes the comment attempt at incident_bridge.py:822, a comment exception is caught and recorded without propagating, and the dry-run path returns at incident_bridge.py:732 before the comment block is reachable. F2: both docstrings and the PR body now state dedupe by provider identity, and the reconcile docstring explicitly records that fingerprint is not populated and plays no part. F3: the PR body describes headbc968dd2, baseedaeede, and validation counts that match this session's independent runs exactly. The remaining acceptance criteria confirmed by reviews 479 and 481 were re-verified and still hold, and the change is purely additive against master with zero deletions in any source file, so it cannot regress existing server behavior.ISSUE:
#607
HEAD_SHA:
bc968dd2e0REVIEW_STATUS:
APPROVE
MERGE_READY:
yes
BLOCKERS:
Blocker classification: none. No code-defect blocker, no evidence-integrity blocker, and no process blocker was found at this head. The two full-suite failures recorded under VALIDATION are pre-existing #737 master drift, were reproduced on pristine master
edaeedein this session, are outside this PR's diff, and are not attributed to PR #767. A merger must still perform its own independent lease, approval-visibility, and mergeability reassessment before merging; this verdict is an approval and is not merge authorization.VALIDATION:
All commands run from a clean detached reviewer worktree /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767-bc968dd2 at
bc968dd2, runner /opt/homebrew/bin/python3, pytest 9.0.3. Focused tests/test_sentry_incident_bridge.py -q -s: 43 passed. Shared substrate tests/test_incident_bridge.py + tests/test_sentry_incident_bridge.py + tests/test_sentry_observability.py -q -s: 94 passed. Full suite -q -s: 2 failed, 3780 passed, 6 skipped, 493 subtests passed in 61.39s. The two failures are tests/test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe and tests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue. Both were independently reproduced in this session on the pristine detached baseline worktree branches/baseline-master-767-review at current masteredaeede250, with the identical verify_preflight_purity signature mismatch, so both are pre-existing #737 drift and were not repaired here. These counts match the PR body exactly. Diff scope against masteredaeede: 5 paths, 2045 insertions, 1 deletion, with numstat showing 466/0, 143/0, 718/0, and 665/0 for gitea_mcp_server.py, incident_bridge.py, sentry_incident_bridge.py, and tests/test_sentry_incident_bridge.py respectively — zero deletions in every source and test file, the single deletion being one replaced documentation line. git diff --check clean. Ancestry:716fc21aremains an ancestor ofbc968dd2, so no force-push or history rewrite occurred. Published-content verification: all five paths atbc968dd2are byte-identical by git blob SHA to the author's verified local integrated commit f0274bc4, including gitea_mcp_server.py ata12717166b, which the author could not blob-compare; git diff f0274bc4bc968dd2is empty. Reviewer identity sysadmin differs from PR author jcwalker3. Runtime parity verified in_parity=true, restart_required=false, stop_required=false, startup_head == current_head == local master == live prgs/master ==edaeede250. Control checkout verified on master atedaeede250and clean.NATIVE_REVIEW_PROOF:
Native MCP transport, production mode, entrypoint mcp_server, pid 21628, profile prgs-reviewer, identity sysadmin, workflow skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6, final report schema hash a7634e7b8689. Reviewer lease session 21628-f893fba2f0c4, acquisition comment 13204, heartbeat comment 13211. Verdict recorded through gitea_mark_final_review_decision then gitea_submit_pr_review with expected_head_sha pinned to
bc968dd2e0.LAST_UPDATED_BY:
sysadmin / prgs-reviewer / independent review cycle / 2026-07-20
Verdict: APPROVE
The AC4 remediation is present, correct, and honestly described. Every finding from review 481 was re-tested against the code at this head rather than accepted from the author record, and each one is resolved.
F1 (was the blocking finding) — AC4 recurrence comments are implemented correctly
The recurrence path now exists end to end, and each safety property review 481 asked about holds by construction rather than by convention.
Only genuine recurrence comments.
reconcile_incidentcomputesrecurrence = incident_recurred(existing, inc)inside theif existing:branch at incident_bridge.py:744, beforedb.upsert_incident_linkat line 788 overwritesevent_countandlast_seen. The in-code comment states exactly this reason.incident_recurredreturns true only when an integerevent_countstrictly advances, or when a non-emptylast_seendiffers from the stored value. The comment block at line 822 is additionally guarded onoutcome == OUTCOME_UPDATED, so issue creation never emits a recurrence comment.test_recurrence_posts_a_comment_on_the_second_scanasserts the first scan produces OUTCOME_CREATED with zero comments, the second produces exactly one comment on the correct linked issue number, org, and repo, and that no duplicate issue is created.Unchanged input produces no duplicate comment. A rescan with identical event state takes the
not recurredbranch and records{"posted": False, "reason": "no new provider events since the last sync"}.test_repeat_scan_without_new_events_posts_no_commentasserts OUTCOME_UPDATED with an empty comment list andpostedfalse, which is the exactly-once property AC4 needs.Dry-run and disabled modes produce no mutation. Two independent gates enforce this.
reconcile_incidentreturns at incident_bridge.py:732 whenapplyis false, so the comment block at 822 is unreachable on a preview. Separately,_incident_recurrence_comment_fnreturnsNonewhenapplyis false and when_profile_operation_gate("gitea.issue.comment")reports the operation blocked, so a profile without issue-comment permission degrades to a withheld comment rather than an error.test_dry_run_scan_posts_no_recurrence_commentasserts OUTCOME_PREVIEW with zero comments even against an already-linked recurrence.Redaction is reused, not re-implemented.
build_recurrence_comment_bodyroutes the summary through the sameredact_textcall used bybuild_gitea_issue_body, and carries a strict subset of that body's fields, so it introduces no field that the issue body does not already publish through the same upstream_cleansanitization.test_recurrence_comment_is_redactedinjectstoken=abc123supersecretinto the Sentry metadata and asserts neither that value nor the configured token appears in the comment body.Incident mapping is durable before the comment attempt.
db.upsert_incident_linkcompletes at line 788, and its own failure path returns at line 816 with OUTCOME_BLOCKED before any comment is attempted. Only after a successful upsert does control reach the comment block.Comment failure does not corrupt the mapping. The
comment_issue_fncall is wrapped intry/except Exception, and the handler records a redacted reason onrecurrence_commentwithout re-raising; execution continues to line 877 wheredb_mutatedandsuccessare set. A dict result reportingsuccess: falseis likewise recorded with its redacted reasons rather than raised.test_recurrence_comment_failure_keeps_the_link_durableinjects a comment callable that raises and asserts OUTCOME_UPDATED,postedfalse, and that the link row still shows the advancedevent_countof 42.F2 (was non-blocking) — the dedupe claim now matches behavior
The
gitea_sentry_reconcile_issuedocstring states dedupe by provider identity — provider, base URL, org, project, and Sentry issue id — and goes further than required by recording explicitly that this bridge does not populatefingerprint, so fingerprint plays no part in its dedupe decisions. Thegitea_sentry_watchdogdocstring states the same basis, and additionally documents the AC4 comment route and itsgitea.issue.commentrequirement including the withheld-comment degradation. The PR body summary and acceptance-criteria list now both state provider identity. No fingerprint dedupe claim remains anywhere in the changed surface.F3 (was non-blocking, record integrity) — the PR body describes this PR
The body states head
bc968dd2, base master atedaeedewith 0 commits behind, and validation of 43 focused passed plus a full suite of 2 failed, 3780 passed, 6 skipped, 493 subtests passed. Every one of those figures reproduced exactly in this session from an independent worktree. The AC4 entry now describes what the code does, including the OUTCOME_UPDATED gating, the new-events condition, dry-run silence, and the five regression tests. The baseline-comparison section names its baseline worktree, its pristine master SHA, the exact node ids, and the matching failure signature.Independent verification of the published content
The author record noted that gitea_mcp_server.py was published from the same atomic
gitea_commit_filescall but was not blob-compared, because reading it back in full was impractical in that session, and invited a reviewer working from a checkout to compare it directly. That comparison was performed here and is clean:git diff f0274bc4 bc968dd2is empty, and all five paths match by blob SHA, including gitea_mcp_server.py ata12717166b. The published head therefore carries exactly the content the author validated locally, with no substitution or truncation on any file.Re-verified and still correct at this head
The change is purely additive against master: numstat reports zero deletions in gitea_mcp_server.py, incident_bridge.py, sentry_incident_bridge.py, and the test file, with the single deletion in the diff being one replaced documentation line. No existing server code is modified, so the #768/#769 recovery path merged at
edaeedecannot be regressed by this branch. All five tools gategitea.readat entry, the watchdog additionally requires issue-create permission before apply and refuses apply unless MCP_SENTRY_ISSUE_BRIDGE_ENABLED is set, and dry-run remains the default on every writing tool.incident_linkson the #613 control-plane DB remains the sole durable mapping authority, and the newCommentIssueFnhook adds a comment route without introducing a second linking substrate. Redaction, failure mapping, pagination, AC6 restart persistence, and AC7 non-reopen behavior were confirmed unchanged and still proven by their tests. The diff stays inside issue #607 with no generated artifacts, caches, credentials, or provenance markers.[THREAD STATE LEDGER] PR #767 / Issue #607 — APPROVE recorded at
bc968dd2What is true now:
bc968dd2e0, matching the pinned head, re-verified immediately before this submission716fc21aremains an ancestor of the current head; the publication was additive with no force-push and no history rewritecb6ae0cadismissed and stale; review 481 at716fc21aundismissed but stale, applying to a superseded head; this review records one native APPROVE at the current headbc968dd2byte-identical by blob SHA to the author's verified local integrated commit f0274bc4, gitea_mcp_server.py includededaeedeWhat changed:
bc968dd2, and ran git fetch --prune prgs on the control checkout to obtain the pinned head object. No file edits, no commits, no pushes, no branch deletion, no worktree deletion. The control checkout stayed on master atedaeede250and clean and was not used for task work.What is blocked:
Who/what acts next:
bc968dd2e0, verifies this APPROVE is visible, undismissed, and not stale at that exact head, reassesses checks, commits behind, conflicts, and mergeability, and merges through the sanctioned MCP merge operation with explicit operator authorizationbc968dd2e0, merger phaserepo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 21628-f893fba2f0c4
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-767-bc968dd2
phase: released
candidate_head:
bc968dd2e0target_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T09:32:54Z
expires_at: 2026-07-20T09:42:54Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #767
issue: #607
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 50748-d1b3a3039126
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-767
phase: claimed
candidate_head:
bc968dd2e0target_branch: master
target_branch_sha:
edaeede250last_activity: 2026-07-20T09:44:00Z
expires_at: 2026-07-20T09:54:00Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-20T09:45:12.519570+00:00approveon PR feat: add Sentry-to-Gitea incident bridge for MCP workflow failures (Closes #607) (#767)closed(merged=True)059ee77c1fd483da385d6ccb4d842e808fb546302prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.