feat: add Sentry-to-Gitea incident bridge for MCP workflow failures (Closes #607) #767

Merged
sysadmin merged 4 commits from feat/issue-607-sentry-incident-bridge into master 2026-07-20 04:45:11 -05:00
Owner

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.py provides config loading, token resolution, paginated Sentry reads, sanitization, and watchdog policy. Five MCP tools are registered in gitea_mcp_server.py:

  • gitea_sentry_list_issues — paginated unresolved-issue reads with query/severity/window filters
  • gitea_sentry_get_issue_events — sanitized recent events for one Sentry issue
  • gitea_sentry_reconcile_issue — decides create / update / ignore, deduplicated by Sentry issue id
  • gitea_sentry_link_gitea_issue — upserts the durable incident_links mapping only; never creates or reopens a Gitea issue
  • gitea_sentry_watchdog — scan job, dry-run by default, apply=true required to write

Durable mapping is delegated to the existing incident_bridge / gitea_observability_link_issue substrate rather than introducing a second workflow authority, so the mapping survives process restarts. Redaction routes strings through sentry_observability.sanitize_path to 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 (at 716fc21a) raised:

  • F1 (blocking) — issue #607 AC4 recurrence comments were not implemented, while this body claimed AC4 was met.
  • F2 (non-blocking) — the "deduplicated by Sentry issue id and fingerprint" claim was unsupported, because observation_from_issue never populates a fingerprint.
  • F3 (non-blocking, record integrity) — this body documented a stale head and base and stale validation counts.

All three are remediated at the current head:

  • F1incident_bridge.incident_recurred() and build_recurrence_comment_body() are added, and a comment_issue_fn hook posts a sanitized recurrence comment on the OUTCOME_UPDATED path only when the provider reports genuinely new events (event_count or last_seen advanced against the pre-upsert link row). The durable incident_links row 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() in gitea_mcp_server.py routes through the sanctioned gitea_create_issue_comment path and returns None on dry runs and when the active profile lacks gitea.issue.comment, so the AC8 dry-run default and disabled-mode safety hold. build_recurrence_comment_body() reuses the same redact_text path as build_gitea_issue_body, so redaction is not re-implemented.
  • F2 — the gitea_sentry_reconcile_issue and gitea_sentry_watchdog docstrings and this body now state the real dedupe basis: provider identity (provider, base URL, org, project, Sentry issue id). Fingerprint is no longer claimed.
  • F3 — this body is updated to the current head, current base, and re-run validation counts below.

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_fn threaded through watchdog, per-issue recurrence_comment recorded in scan results)
  • incident_bridge.py (incident_recurred, build_recurrence_comment_body, CommentIssueFn, recurrence-comment path on OUTCOME_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.md

Every changed file is in scope for this issue. No generated artifacts, caches, credentials, temporary files, or provenance markers are present.

Branch and head

  • Branch: feat/issue-607-sentry-incident-bridge
  • Head SHA: bc968dd2e05a10529b08da2dea511d61a2404409
  • Base: master at edaeede250f46f361784061f025ab0e867172d2c (0 commits behind)
  • Author worktree: /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: remediation a487f9fdc94388252b9a234976ca0c9c9ea7f706 and published head 716fc21a0dd6521d71b6335f362f658654b5f69e), then published through gitea_commit_files as server-side commit bc968dd2 onto the published head. No force-push and no history rewrite: 716fc21a remains 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.

  1. Focused: /opt/homebrew/bin/python3 -m pytest tests/test_sentry_incident_bridge.py -q -s -> 43 passed, exit 0.
  2. Full suite: /opt/homebrew/bin/python3 -m pytest -q -s -> 2 failed, 3780 passed, 6 skipped, 493 subtests passed in 56.71s.
  3. Diff check: git diff --check -> clean.

Baseline comparison for the two full-suite failures

  • Baseline worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/baseline-master-607-remediation
  • Baseline target SHA: edaeede250f46f361784061f025ab0e867172d2c (pristine current master, detached)
  • Task branch integrated SHA: f0274bc4c08e0a7eeceeec31375b2b6179043755
  • Command run on the baseline worktree: /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 -s
  • Baseline result: 2 failed — both fail on pristine master.
  • Task branch failures: the identical two node ids.
  • Signature match: test_reconciler_supersession_close fails on both with AssertionError: 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_f6 fails on both.
  • Baseline worktree clean before and after the run; issue worktree clean before and after the run.

These two failures are the known pre-existing master drift from #737 (the workspace-repo forwarding change added org/repo kwargs 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

  1. Reads unresolved issues/events from the configured self-hosted Sentry base URL using env-only credentials.
  2. Sentry data is sanitized before any Gitea write.
  3. Dedupe by provider identity (provider, base URL, org, project, Sentry issue id) produces one durable Gitea issue, not duplicates.
  4. Continued events post a sanitized recurrence comment on the linked issue — implemented on the OUTCOME_UPDATED path, gated on genuinely new events, dry-run silent, and covered by five regression tests.
  5. Issue content carries permalink, event count, first_seen, last_seen, affected tool, blocker type, and canonical next action.
  6. Mapping persists across restarts via the durable incident_links store.
  7. Closed linked issues are not reopened.
  8. Watchdog defaults to dry-run; apply=true is required to write, and dry runs never comment.
  9. Tests cover create, update, dedupe, recurrence comment, dry-run silence, no-new-events silence, comment-failure link durability, comment redaction, closed-linked issue, redaction, pagination, missing token, unavailable Sentry server, and self-hosted base URL.
  10. Gitea stays the canonical durable record.

Risk

Low. All new surface is opt-in behind MCP_SENTRY_ISSUE_BRIDGE_ENABLED / MCP_SENTRY_ENABLED and 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

  • Sentry writeback (tagging the Sentry issue with the Gitea issue number) is only attempted where the deployed Sentry API supports it; otherwise the backlink lives in Gitea plus the bridge mapping.
  • Live end-to-end verification against the self-hosted Sentry host is not part of this PR; tests use injected HTTP fixtures.
  • The two full-suite failures above remain, tracked under the pre-existing #737 drift.

Workflow evidence

  • Router: skills/llm-project-workflow/SKILL.md; workflow: skills/llm-project-workflow/workflows/work-issue.md (task_mode: work-issue, canonical).
  • Namespace gitea-tools, profile prgs-author, role author, identity jcwalker3, remote prgs.
  • Master parity: startup_head == current_head == local master == live prgs/master == edaeede250f46f361784061f025ab0e867172d2c, in_parity=true, restart_required=false, stop_required=false.
  • Dead-session lock recovery via 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 head 716fc21a, accepted head f0274bc4.
  • Adoption proof: adoption_decision=ADOPT, no_existing_pr_proof=true, no_competing_live_lock_proof=true, competing-branch check clear.
  • Publication: gitea_commit_files with all four payloads sourced from workspace_path in the bound author worktree. Read-after-write verified: PR head moved 716fc21a -> bc968dd2; incident_bridge.py, sentry_incident_bridge.py, and tests/test_sentry_incident_bridge.py confirmed byte-identical to the verified local worktree by git blob SHA (df41ad9bd957a92cfda83801a468202190b2412d, 56bab8ea4ed77564f2ac7f5c5c6b83c64e3a0460, 8f19510b61d2fda4baf1e60ed48ca30a908d5e96).
  • Control checkout stayed on master at edaeede250f4, clean, and was not used for task work.

Author role only. This PR must be reviewed and merged by an independent reviewer and merger.

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.py` provides config loading, token resolution, paginated Sentry reads, sanitization, and watchdog policy. Five MCP tools are registered in `gitea_mcp_server.py`: - `gitea_sentry_list_issues` — paginated unresolved-issue reads with query/severity/window filters - `gitea_sentry_get_issue_events` — sanitized recent events for one Sentry issue - `gitea_sentry_reconcile_issue` — decides create / update / ignore, deduplicated by Sentry issue id - `gitea_sentry_link_gitea_issue` — upserts the durable `incident_links` mapping only; never creates or reopens a Gitea issue - `gitea_sentry_watchdog` — scan job, dry-run by default, `apply=true` required to write Durable mapping is delegated to the existing `incident_bridge` / `gitea_observability_link_issue` substrate rather than introducing a second workflow authority, so the mapping survives process restarts. Redaction routes strings through `sentry_observability.sanitize_path` to coarse path categories, and Sentry writes fail closed on redaction failure while non-bridge MCP tools fail open on Sentry outages. ## Linked issue - Closes #607 - Depends on #606 (Sentry SDK observability instrumentation) — #606 is closed. ## Review remediation history Two REQUEST_CHANGES verdicts were recorded against earlier heads. **Review 479** (at `cb6ae0ca`) and **review 481** (at `716fc21a`) raised: - **F1 (blocking)** — issue #607 AC4 recurrence comments were not implemented, while this body claimed AC4 was met. - **F2 (non-blocking)** — the "deduplicated by Sentry issue id and fingerprint" claim was unsupported, because `observation_from_issue` never populates a fingerprint. - **F3 (non-blocking, record integrity)** — this body documented a stale head and base and stale validation counts. All three are remediated at the current head: - **F1** — `incident_bridge.incident_recurred()` and `build_recurrence_comment_body()` are added, and a `comment_issue_fn` hook posts a sanitized recurrence comment on the `OUTCOME_UPDATED` path only when the provider reports genuinely new events (`event_count` or `last_seen` advanced against the pre-upsert link row). The durable `incident_links` row 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()` in `gitea_mcp_server.py` routes through the sanctioned `gitea_create_issue_comment` path and returns `None` on dry runs and when the active profile lacks `gitea.issue.comment`, so the AC8 dry-run default and disabled-mode safety hold. `build_recurrence_comment_body()` reuses the same `redact_text` path as `build_gitea_issue_body`, so redaction is not re-implemented. - **F2** — the `gitea_sentry_reconcile_issue` and `gitea_sentry_watchdog` docstrings and this body now state the real dedupe basis: provider identity (provider, base URL, org, project, Sentry issue id). Fingerprint is no longer claimed. - **F3** — this body is updated to the current head, current base, and re-run validation counts below. 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_fn` threaded through `watchdog`, per-issue `recurrence_comment` recorded in scan results) - `incident_bridge.py` (`incident_recurred`, `build_recurrence_comment_body`, `CommentIssueFn`, recurrence-comment path on `OUTCOME_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.md` Every changed file is in scope for this issue. No generated artifacts, caches, credentials, temporary files, or provenance markers are present. ## Branch and head - Branch: `feat/issue-607-sentry-incident-bridge` - Head SHA: `bc968dd2e05a10529b08da2dea511d61a2404409` - Base: `master` at `edaeede250f46f361784061f025ab0e867172d2c` (0 commits behind) - Author worktree: `/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: remediation `a487f9fdc94388252b9a234976ca0c9c9ea7f706` and published head `716fc21a0dd6521d71b6335f362f658654b5f69e`), then published through `gitea_commit_files` as server-side commit `bc968dd2` onto the published head. No force-push and no history rewrite: `716fc21a` remains 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. 1. Focused: `/opt/homebrew/bin/python3 -m pytest tests/test_sentry_incident_bridge.py -q -s` -> **43 passed**, exit 0. 2. Full suite: `/opt/homebrew/bin/python3 -m pytest -q -s` -> **2 failed, 3780 passed, 6 skipped, 493 subtests passed** in 56.71s. 3. Diff check: `git diff --check` -> clean. ### Baseline comparison for the two full-suite failures - Baseline worktree: `/Users/jasonwalker/Development/Gitea-Tools/branches/baseline-master-607-remediation` - Baseline target SHA: `edaeede250f46f361784061f025ab0e867172d2c` (pristine current master, detached) - Task branch integrated SHA: `f0274bc4c08e0a7eeceeec31375b2b6179043755` - Command run on the baseline worktree: `/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 -s` - Baseline result: **2 failed** — both fail on pristine master. - Task branch failures: the identical two node ids. - Signature match: `test_reconciler_supersession_close` fails on both with `AssertionError: 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_f6` fails on both. - Baseline worktree clean before and after the run; issue worktree clean before and after the run. These two failures are the known pre-existing master drift from #737 (the workspace-repo forwarding change added `org`/`repo` kwargs 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 1. Reads unresolved issues/events from the configured self-hosted Sentry base URL using env-only credentials. 2. Sentry data is sanitized before any Gitea write. 3. Dedupe by provider identity (provider, base URL, org, project, Sentry issue id) produces one durable Gitea issue, not duplicates. 4. **Continued events post a sanitized recurrence comment on the linked issue** — implemented on the `OUTCOME_UPDATED` path, gated on genuinely new events, dry-run silent, and covered by five regression tests. 5. Issue content carries permalink, event count, first_seen, last_seen, affected tool, blocker type, and canonical next action. 6. Mapping persists across restarts via the durable `incident_links` store. 7. Closed linked issues are not reopened. 8. Watchdog defaults to dry-run; `apply=true` is required to write, and dry runs never comment. 9. Tests cover create, update, dedupe, recurrence comment, dry-run silence, no-new-events silence, comment-failure link durability, comment redaction, closed-linked issue, redaction, pagination, missing token, unavailable Sentry server, and self-hosted base URL. 10. Gitea stays the canonical durable record. ## Risk Low. All new surface is opt-in behind `MCP_SENTRY_ISSUE_BRIDGE_ENABLED` / `MCP_SENTRY_ENABLED` and 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 - Sentry writeback (tagging the Sentry issue with the Gitea issue number) is only attempted where the deployed Sentry API supports it; otherwise the backlink lives in Gitea plus the bridge mapping. - Live end-to-end verification against the self-hosted Sentry host is not part of this PR; tests use injected HTTP fixtures. - The two full-suite failures above remain, tracked under the pre-existing #737 drift. ## Workflow evidence - Router: `skills/llm-project-workflow/SKILL.md`; workflow: `skills/llm-project-workflow/workflows/work-issue.md` (`task_mode: work-issue`, canonical). - Namespace `gitea-tools`, profile `prgs-author`, role author, identity `jcwalker3`, remote `prgs`. - Master parity: `startup_head == current_head == local master == live prgs/master == edaeede250f46f361784061f025ab0e867172d2c`, `in_parity=true`, `restart_required=false`, `stop_required=false`. - Dead-session lock recovery via `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 head `716fc21a`, accepted head `f0274bc4`. - Adoption proof: `adoption_decision=ADOPT`, `no_existing_pr_proof=true`, `no_competing_live_lock_proof=true`, competing-branch check clear. - Publication: `gitea_commit_files` with all four payloads sourced from `workspace_path` in the bound author worktree. Read-after-write verified: PR head moved `716fc21a` -> `bc968dd2`; `incident_bridge.py`, `sentry_incident_bridge.py`, and `tests/test_sentry_incident_bridge.py` confirmed byte-identical to the verified local worktree by git blob SHA (`df41ad9bd957a92cfda83801a468202190b2412d`, `56bab8ea4ed77564f2ac7f5c5c6b83c64e3a0460`, `8f19510b61d2fda4baf1e60ed48ca30a908d5e96`). - Control checkout stayed on `master` at `edaeede250f4`, clean, and was not used for task work. Author role only. This PR must be reviewed and merged by an independent reviewer and merger.
jcwalker3 added 2 commits 2026-07-20 00:33:41 -05:00
Adds the read half of the inbound observability path: pull unresolved issues
and events from the self-hosted Sentry API, normalize them into #612
observations, and reconcile them into durable Gitea issues.

Design: the existing #612 incident_bridge already owns dedupe, linking,
redaction, and issue creation on the #613 incident_links substrate, so this
change adds only what was genuinely missing - a Sentry API read layer,
observation mapping, a policy gate, and a watchdog. No second linking store is
introduced, which is what makes the mapping survive restarts (AC6).

New module sentry_incident_bridge.py:
* list_issues() with Link-header cursor pagination and statsPeriod windowing
* get_issue_events() returning sanitized recent + latest event
* observation_from_issue() mapping onto the #612 observation contract
* should_bridge_issue() policy gate (unresolved + event-count threshold)
* watchdog() scanning and reconciling, dry-run by default
* HTTP access injected as http_fn so the surface is testable without Sentry

New MCP tools: gitea_sentry_list_issues, gitea_sentry_get_issue_events,
gitea_sentry_reconcile_issue, gitea_sentry_link_gitea_issue,
gitea_sentry_watchdog.

Safety:
* SENTRY_AUTH_TOKEN is read from the environment only and never returned,
  logged, or stored; config projections cannot carry it by construction
* missing config fails closed as not_configured, and a missing token fails
  closed as missing_token before any HTTP call is made
* apply=true requires both MCP_SENTRY_ISSUE_BRIDGE_ENABLED and issue-create
  permission; Sentry outages create nothing
* secrets are redacted and absolute local paths reduced to a category token
  before any value can reach a Gitea issue body
* raw Sentry incidents are never assignable control-plane work items

Tests: 38 new cases covering create, update/recurrence, dedupe, resolved-issue
non-reopen, redaction, pagination, missing token, unavailable server,
self-hosted base URL, restart persistence, policy gates, and the five tool
wrappers.

Full suite: 3733 passed, 6 skipped. The 2 remaining failures
(test_issue_702_review_findings_f1_f6, test_reconciler_supersession_close) were
verified to fail identically on unmodified master at fcf6981 and are unrelated
to this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_011w1WGVV3duWEf45SJRJ1DL
Owner

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: cb6ae0ca50
target_branch: master
target_branch_sha: d12adabeb1
last_activity: 2026-07-20T05:44:00Z
expires_at: 2026-07-20T05:54:00Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b target_branch: master target_branch_sha: d12adabeb1cf7810b1286ca8871a9b680646021c last_activity: 2026-07-20T05:44:00Z expires_at: 2026-07-20T05:54:00Z blocker: none
sysadmin requested changes 2026-07-20 00:49:43 -05:00
Dismissed
sysadmin left a comment
Owner

Canonical PR State

STATE:
PR #767 reviewed at pinned head cb6ae0ca50 by 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:

Role: AUTHOR
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
Issue: #607
PR: #767
Current head: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b

Remediate reviewer finding F1 on PR #767. Issue #607 AC4 requires that an
existing linked Gitea issue receives a recurrence comment when Sentry events
continue. That path does not exist: incident_bridge.reconcile_incident only
calls db.upsert_incident_link on the OUTCOME_UPDATED branch, sentry_incident_bridge
threads no comment callable, and no test asserts a comment.

Implement the recurrence comment on the OUTCOME_UPDATED path through the
sanctioned gitea_create_issue_comment route named in issue #607. Apply the same
redaction used for issue bodies. Keep the dry-run default: post no comment when
apply is false. Add tests asserting a comment is posted on the second scan and
that a dry-run scan posts none.

Also address F2: observation_from_issue never sets a fingerprint key, so the
"deduplicated by Sentry issue id and fingerprint" claim in the PR body and in
the gitea_sentry_reconcile_issue docstring is unsupported. Either populate
fingerprint from the Sentry payload or remove fingerprint from the claim.

Do not repair the pre-existing #737 master drift in this PR. Do not delete the
retained baseline worktree branches/baseline-master-issue-607. Push a new head
and stop for an independent reviewer.

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:
cb6ae0ca50

REVIEW_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 d12adabeb1 with 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 at d12adabe. 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:

  • sentry_incident_bridge.py — no comment-posting call exists anywhere in the module. watchdog() (lines 579-712) delegates every per-issue outcome to reconcile_fn / incident_bridge.reconcile_incident and then only records the returned outcome.
  • incident_bridge.py (unchanged by this PR) contains no comment-posting call either. On the recurrence path, reconcile_incident takes the existing-link branch, sets action="updated_existing_link" and OUTCOME_UPDATED, and its only write is db.upsert_incident_link(...). The linked Gitea issue itself is never touched after creation.
  • gitea_mcp_server.py — the five new registrations pass only create_issue_fn into the watchdog. No gitea_create_issue_comment callable is threaded through, so the bridge has no route to a comment even in principle.
  • tests/test_sentry_incident_bridge.py — test_recurrence_updates_link_event_count (line 375) asserts only that the incident_links row event_count becomes 42. It verifies the DB row, not a comment on the Gitea issue. No test asserts recurrence comment content, and none can, because the behavior is absent.

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

  • All five tools carry @mcp.tool() registrations and gate gitea.read at entry. gitea_sentry_watchdog additionally requires create_issue permission before apply, and gitea_sentry_reconcile_issue and gitea_sentry_link_gitea_issue inherit the same gate through gitea_observability_reconcile_incident. Capability boundaries are correct and fail closed.
  • 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. No second linking substrate is introduced. create_issue_fn matches the declared CreateIssueFn signature.
  • Redaction is sound. _clean correctly applies sanitize_path only to substrings matching an absolute path rather than to whole free-text fields, permalinks carrying credentials are dropped, and sensitive tag keys are removed. Tests prove a token value never reaches tool output.
  • Failure handling is correct: missing token and unconfigured org/project fail closed before any HTTP call, 401 and 403 map to missing_token, 5xx maps to sentry_unavailable, and a single bad issue is isolated so it cannot abort a scan.
  • Pagination follows the Link header with a max_pages cap and reports inventory_complete honestly.
  • AC6 restart persistence is genuinely proven by test_link_survives_a_new_db_handle, and AC7 non-reopen by test_resolved_issue_is_not_recreated_or_reopened.
  • The diff stays inside issue #607: four files, no unrelated edits, no generated artifacts or credentials.

[THREAD STATE LEDGER] PR #767 / Issue #607 — REQUEST_CHANGES recorded at cb6ae0ca

What is true now:

  • PR state: open, base master, head feat/issue-607-sentry-incident-bridge, mergeable true
  • Head SHA at review time: cb6ae0ca50, unchanged from the pinned head
  • Server-side decision state: one native REQUEST_CHANGES verdict recorded by sysadmin at this head; no prior verdict existed
  • Reviewer lease: session 48518-ac60c7148b3c, comment 13098
  • Latest known validation: focused 38 passed; full suite 3749 passed, 6 skipped, 2 failed, both failures reproduced on pristine master d12adabe

What changed:

  • Server-side: this review verdict and the reviewer lease comment only
  • Local: no file edits, no commits, no branch or worktree creation or deletion. The control checkout stayed on master and clean. The retained baseline worktree branches/baseline-master-issue-607 was not touched.

What is blocked:

  • Blocker classification: code-defect blocker
  • Issue #607 AC4 is not implemented; the recurrence comment path does not exist

Who/what acts next:

  • Next actor: author
  • Required action: implement the AC4 recurrence comment on the OUTCOME_UPDATED path with tests, correct or substantiate the fingerprint dedupe claim in F2, push a new head, and hand off to a fresh independent reviewer
  • Do not do: do not merge PR #767 at cb6ae0ca; do not repair the pre-existing #737 master drift inside this PR; do not delete the retained baseline worktree; do not treat this REQUEST_CHANGES as resolved without a new head
  • Resume from: PR #767, remediating F1 first
## Canonical PR State STATE: PR #767 reviewed at pinned head cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b by 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: ```text Role: AUTHOR Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools Issue: #607 PR: #767 Current head: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b Remediate reviewer finding F1 on PR #767. Issue #607 AC4 requires that an existing linked Gitea issue receives a recurrence comment when Sentry events continue. That path does not exist: incident_bridge.reconcile_incident only calls db.upsert_incident_link on the OUTCOME_UPDATED branch, sentry_incident_bridge threads no comment callable, and no test asserts a comment. Implement the recurrence comment on the OUTCOME_UPDATED path through the sanctioned gitea_create_issue_comment route named in issue #607. Apply the same redaction used for issue bodies. Keep the dry-run default: post no comment when apply is false. Add tests asserting a comment is posted on the second scan and that a dry-run scan posts none. Also address F2: observation_from_issue never sets a fingerprint key, so the "deduplicated by Sentry issue id and fingerprint" claim in the PR body and in the gitea_sentry_reconcile_issue docstring is unsupported. Either populate fingerprint from the Sentry payload or remove fingerprint from the claim. Do not repair the pre-existing #737 master drift in this PR. Do not delete the retained baseline worktree branches/baseline-master-issue-607. Push a new head and stop for an independent reviewer. ``` 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: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b REVIEW_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 d12adabeb1cf7810b1286ca8871a9b680646021c with 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 at d12adabe. 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: - sentry_incident_bridge.py — no comment-posting call exists anywhere in the module. watchdog() (lines 579-712) delegates every per-issue outcome to reconcile_fn / incident_bridge.reconcile_incident and then only records the returned outcome. - incident_bridge.py (unchanged by this PR) contains no comment-posting call either. On the recurrence path, reconcile_incident takes the existing-link branch, sets action="updated_existing_link" and OUTCOME_UPDATED, and its only write is db.upsert_incident_link(...). The linked Gitea issue itself is never touched after creation. - gitea_mcp_server.py — the five new registrations pass only create_issue_fn into the watchdog. No gitea_create_issue_comment callable is threaded through, so the bridge has no route to a comment even in principle. - tests/test_sentry_incident_bridge.py — test_recurrence_updates_link_event_count (line 375) asserts only that the incident_links row event_count becomes 42. It verifies the DB row, not a comment on the Gitea issue. No test asserts recurrence comment content, and none can, because the behavior is absent. 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 - All five tools carry @mcp.tool() registrations and gate gitea.read at entry. gitea_sentry_watchdog additionally requires create_issue permission before apply, and gitea_sentry_reconcile_issue and gitea_sentry_link_gitea_issue inherit the same gate through gitea_observability_reconcile_incident. Capability boundaries are correct and fail closed. - 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. No second linking substrate is introduced. create_issue_fn matches the declared CreateIssueFn signature. - Redaction is sound. _clean correctly applies sanitize_path only to substrings matching an absolute path rather than to whole free-text fields, permalinks carrying credentials are dropped, and sensitive tag keys are removed. Tests prove a token value never reaches tool output. - Failure handling is correct: missing token and unconfigured org/project fail closed before any HTTP call, 401 and 403 map to missing_token, 5xx maps to sentry_unavailable, and a single bad issue is isolated so it cannot abort a scan. - Pagination follows the Link header with a max_pages cap and reports inventory_complete honestly. - AC6 restart persistence is genuinely proven by test_link_survives_a_new_db_handle, and AC7 non-reopen by test_resolved_issue_is_not_recreated_or_reopened. - The diff stays inside issue #607: four files, no unrelated edits, no generated artifacts or credentials. [THREAD STATE LEDGER] PR #767 / Issue #607 — REQUEST_CHANGES recorded at cb6ae0ca What is true now: - PR state: open, base master, head feat/issue-607-sentry-incident-bridge, mergeable true - Head SHA at review time: cb6ae0ca50ea3fea3beae9c9544c257bf6c9949b, unchanged from the pinned head - Server-side decision state: one native REQUEST_CHANGES verdict recorded by sysadmin at this head; no prior verdict existed - Reviewer lease: session 48518-ac60c7148b3c, comment 13098 - Latest known validation: focused 38 passed; full suite 3749 passed, 6 skipped, 2 failed, both failures reproduced on pristine master d12adabe What changed: - Server-side: this review verdict and the reviewer lease comment only - Local: no file edits, no commits, no branch or worktree creation or deletion. The control checkout stayed on master and clean. The retained baseline worktree branches/baseline-master-issue-607 was not touched. What is blocked: - Blocker classification: code-defect blocker - Issue #607 AC4 is not implemented; the recurrence comment path does not exist Who/what acts next: - Next actor: author - Required action: implement the AC4 recurrence comment on the OUTCOME_UPDATED path with tests, correct or substantiate the fingerprint dedupe claim in F2, push a new head, and hand off to a fresh independent reviewer - Do not do: do not merge PR #767 at cb6ae0ca; do not repair the pre-existing #737 master drift inside this PR; do not delete the retained baseline worktree; do not treat this REQUEST_CHANGES as resolved without a new head - Resume from: PR #767, remediating F1 first
jcwalker3 added 1 commit 2026-07-20 03:08:55 -05:00
Owner

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: 716fc21a0d
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T08:33:14Z
expires_at: 2026-07-20T08:43:14Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: 716fc21a0dd6521d71b6335f362f658654b5f69e target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T08:33:14Z expires_at: 2026-07-20T08:43:14Z blocker: none
sysadmin requested changes 2026-07-20 03:37:16 -05:00
Dismissed
sysadmin left a comment
Owner

Canonical PR State

STATE:
PR #767 re-reviewed at pinned head 716fc21a0d by a fresh independent reviewer. The head advanced from cb6ae0ca by 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:

Role: AUTHOR
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
Issue: #607
PR: #767
Current head: 716fc21a0dd6521d71b6335f362f658654b5f69e

Review 479 findings F1 and F2 are still unremediated on the published PR head.
The head moved from cb6ae0ca to 716fc21a, but that move is a merge of master
edaeede into the branch and nothing else. All three PR-scope files are
byte-identical between cb6ae0ca and 716fc21a.

The remediation already exists as unpushed local commit
a487f9fdc94388252b9a234976ca0c9c9ea7f706 in worktree
branches/issue-607-sentry-incident-bridge. Its message describes exactly the
F1 fix requested by review 479: incident_bridge.incident_recurred(),
build_recurrence_comment_body(), a comment_issue_fn hook posted only on
OUTCOME_UPDATED with genuinely new events, the durable incident_links row
written first so a comment failure cannot roll back the mapping,
_incident_recurrence_comment_fn() routed through gitea_create_issue_comment,
returning None on dry runs and when the profile lacks gitea.issue.comment, plus
the F2 dedupe-basis correction. It carries 43 test defs against 38 at the
published head.

Do not re-implement this work. Publish the existing commit to the PR branch.
The #768/#769 strict-descendant dead-session recovery fix is now on master at
edaeede and is the sanctioned route out of the re-lock deadlock recorded in
issue #607 comment 13104: local head a487f9f is a strict descendant of the head
recorded in the durable lock, which is the case that fix accepts. Re-run
gitea_lock_issue for #607 and confirm the response sanctions recovery, then
publish.

Note that local branch feat/issue-607-sentry-incident-bridge is currently 1
ahead and 3 behind the remote, so a487f9f must be integrated with the published
head 716fc21a rather than replacing it. Do not force-push. Do not repair the
pre-existing #737 master drift in this PR. Do not delete the retained baseline
worktrees.

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 cb6ae0ca to 716fc21a. Ancestry analysis showed 716fc21a is a two-parent merge of cb6ae0ca and master edaeede, 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 cb6ae0ca is still present at 716fc21a, 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:
716fc21a0d

REVIEW_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 cb6ae0ca and base d12adab and 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 master edaeede250 with 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 master edaeede, not the older d12adab the PR body cites. Diff scope confirmed against master edaeede: 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 cb6ae0ca because 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 at 716fc21a.

Evidence gathered at the pinned head:

  • Ancestry: git rev-list --parents -n 1 716fc21a gives parents cb6ae0ca and edaeede. The head advance is a merge of master into the branch. git merge-base --is-ancestor a487f9f 716fc21a returns false, so the remediation commit is not contained in the published head.
  • Content: all three remaining PR-scope files are byte-identical between cb6ae0ca and 716fc21a by blob hash — sentry_incident_bridge.py, tests/test_sentry_incident_bridge.py, and docs/observability/sentry-integration.md. git diff cb6ae0ca 716fc21a over 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.
  • Direct source inspection at 716fc21a: git show 716fc21a:sentry_incident_bridge.py matched against comment, recurrence, and fingerprint returns no hits at all. There is no comment-posting call, and no fingerprint key, anywhere in the module.
  • Test surface at 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 716fc21a and live master is edaeede. 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 edaeede into this branch was checked specifically, since the review asked about integration with the #768/#769 recovery work now present on master:

  • Against master edaeede the 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.
  • The full suite at the pinned head passes 3775 with only the two pre-existing #737 failures, both reproduced on pristine master edaeede in this session. The merge introduced no new failure.
  • Worth noting for the author: #769 is what unblocks the deadlock recorded in issue #607 comment 13104. That comment stopped because committing the remediation advanced local HEAD past the head recorded in the durable lock, dropping recovery sanction. The strict-descendant acceptance merged as #769 is exactly that case, so the sanctioned publication route now exists.

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 716fc21a

What is true now:

  • PR state: open, base master, head branch feat/issue-607-sentry-incident-bridge, mergeable true
  • Head SHA at review time: 716fc21a0d, matching the pinned head, re-verified immediately before submission
  • The head advanced from cb6ae0ca by a merge of master edaeede and nothing else; remediation commit a487f9f is not an ancestor of the published head
  • Server-side decision state: review 479 REQUEST_CHANGES at cb6ae0ca, undismissed and marked stale by Gitea; this review adds a second REQUEST_CHANGES at the current head
  • Reviewer lease: session 44257-b557247ab1ee, comment 13161
  • Latest validation: focused 38 passed; full suite 3775 passed, 6 skipped, 2 failed, both failures reproduced on pristine current master edaeede
  • The F1/F2 remediation exists only as unpushed local commit a487f9f in branches/issue-607-sentry-incident-bridge, which is 1 ahead and 3 behind the remote branch

What changed:

  • Server-side: this review verdict and the reviewer lease comment 13161 only
  • Local: created two clean detached worktrees for this review, branches/review-pr-767 at 716fc21a and branches/baseline-master-767-review at edaeede. No file edits, no commits, no pushes, no branch or worktree deletion. The control checkout stayed on master at edaeede and clean. The author worktree branches/issue-607-sentry-incident-bridge and both retained baseline worktrees were not modified.

What is blocked:

  • Blocker classification: code-defect blocker
  • Issue #607 AC4 is not implemented on the published PR head; the recurrence comment path does not exist at 716fc21a

Who/what acts next:

  • Next actor: author
  • Required action: publish the existing remediation commit a487f9f onto the current published head 716fc21a using 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 reviewer
  • Do not do: do not merge PR #767 at 716fc21a; do not treat review 479 or this verdict as resolved by the master merge, since author_pushed_after_request_changes=true is misleading here; do not re-implement the remediation, as a487f9f already contains it; do not force-push over the published head; do not repair the pre-existing #737 master drift inside this PR; do not delete the retained baseline worktrees
  • Resume from: PR #767 at head 716fc21a, publishing a487f9f first
## Canonical PR State STATE: PR #767 re-reviewed at pinned head 716fc21a0dd6521d71b6335f362f658654b5f69e by a fresh independent reviewer. The head advanced from cb6ae0ca by 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: ```text Role: AUTHOR Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools Issue: #607 PR: #767 Current head: 716fc21a0dd6521d71b6335f362f658654b5f69e Review 479 findings F1 and F2 are still unremediated on the published PR head. The head moved from cb6ae0ca to 716fc21a, but that move is a merge of master edaeede into the branch and nothing else. All three PR-scope files are byte-identical between cb6ae0ca and 716fc21a. The remediation already exists as unpushed local commit a487f9fdc94388252b9a234976ca0c9c9ea7f706 in worktree branches/issue-607-sentry-incident-bridge. Its message describes exactly the F1 fix requested by review 479: incident_bridge.incident_recurred(), build_recurrence_comment_body(), a comment_issue_fn hook posted only on OUTCOME_UPDATED with genuinely new events, the durable incident_links row written first so a comment failure cannot roll back the mapping, _incident_recurrence_comment_fn() routed through gitea_create_issue_comment, returning None on dry runs and when the profile lacks gitea.issue.comment, plus the F2 dedupe-basis correction. It carries 43 test defs against 38 at the published head. Do not re-implement this work. Publish the existing commit to the PR branch. The #768/#769 strict-descendant dead-session recovery fix is now on master at edaeede and is the sanctioned route out of the re-lock deadlock recorded in issue #607 comment 13104: local head a487f9f is a strict descendant of the head recorded in the durable lock, which is the case that fix accepts. Re-run gitea_lock_issue for #607 and confirm the response sanctions recovery, then publish. Note that local branch feat/issue-607-sentry-incident-bridge is currently 1 ahead and 3 behind the remote, so a487f9f must be integrated with the published head 716fc21a rather than replacing it. Do not force-push. Do not repair the pre-existing #737 master drift in this PR. Do not delete the retained baseline worktrees. ``` 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 cb6ae0ca to 716fc21a. Ancestry analysis showed 716fc21a is a two-parent merge of cb6ae0ca and master edaeede, 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 cb6ae0ca is still present at 716fc21a, 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: 716fc21a0dd6521d71b6335f362f658654b5f69e REVIEW_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 cb6ae0ca and base d12adab and 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 master edaeede250f46f361784061f025ab0e867172d2c with 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 master edaeede, not the older d12adab the PR body cites. Diff scope confirmed against master edaeede: 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 cb6ae0ca because 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 at 716fc21a. Evidence gathered at the pinned head: - Ancestry: `git rev-list --parents -n 1 716fc21a` gives parents cb6ae0ca and edaeede. The head advance is a merge of master into the branch. `git merge-base --is-ancestor a487f9f 716fc21a` returns false, so the remediation commit is not contained in the published head. - Content: all three remaining PR-scope files are byte-identical between cb6ae0ca and 716fc21a by blob hash — sentry_incident_bridge.py, tests/test_sentry_incident_bridge.py, and docs/observability/sentry-integration.md. `git diff cb6ae0ca 716fc21a` over 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. - Direct source inspection at 716fc21a: `git show 716fc21a:sentry_incident_bridge.py` matched against `comment`, `recurrence`, and `fingerprint` returns no hits at all. There is no comment-posting call, and no fingerprint key, anywhere in the module. - Test surface at 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 716fc21a and live master is edaeede. 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 edaeede into this branch was checked specifically, since the review asked about integration with the #768/#769 recovery work now present on master: - Against master edaeede the 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. - The full suite at the pinned head passes 3775 with only the two pre-existing #737 failures, both reproduced on pristine master edaeede in this session. The merge introduced no new failure. - Worth noting for the author: #769 is what unblocks the deadlock recorded in issue #607 comment 13104. That comment stopped because committing the remediation advanced local HEAD past the head recorded in the durable lock, dropping recovery sanction. The strict-descendant acceptance merged as #769 is exactly that case, so the sanctioned publication route now exists. ### 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 716fc21a What is true now: - PR state: open, base master, head branch feat/issue-607-sentry-incident-bridge, mergeable true - Head SHA at review time: 716fc21a0dd6521d71b6335f362f658654b5f69e, matching the pinned head, re-verified immediately before submission - The head advanced from cb6ae0ca by a merge of master edaeede and nothing else; remediation commit a487f9f is not an ancestor of the published head - Server-side decision state: review 479 REQUEST_CHANGES at cb6ae0ca, undismissed and marked stale by Gitea; this review adds a second REQUEST_CHANGES at the current head - Reviewer lease: session 44257-b557247ab1ee, comment 13161 - Latest validation: focused 38 passed; full suite 3775 passed, 6 skipped, 2 failed, both failures reproduced on pristine current master edaeede - The F1/F2 remediation exists only as unpushed local commit a487f9f in branches/issue-607-sentry-incident-bridge, which is 1 ahead and 3 behind the remote branch What changed: - Server-side: this review verdict and the reviewer lease comment 13161 only - Local: created two clean detached worktrees for this review, branches/review-pr-767 at 716fc21a and branches/baseline-master-767-review at edaeede. No file edits, no commits, no pushes, no branch or worktree deletion. The control checkout stayed on master at edaeede and clean. The author worktree branches/issue-607-sentry-incident-bridge and both retained baseline worktrees were not modified. What is blocked: - Blocker classification: code-defect blocker - Issue #607 AC4 is not implemented on the published PR head; the recurrence comment path does not exist at 716fc21a Who/what acts next: - Next actor: author - Required action: publish the existing remediation commit a487f9f onto the current published head 716fc21a using 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 reviewer - Do not do: do not merge PR #767 at 716fc21a; do not treat review 479 or this verdict as resolved by the master merge, since author_pushed_after_request_changes=true is misleading here; do not re-implement the remediation, as a487f9f already contains it; do not force-push over the published head; do not repair the pre-existing #737 master drift inside this PR; do not delete the retained baseline worktrees - Resume from: PR #767 at head 716fc21a, publishing a487f9f first
Owner

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: released
candidate_head: 716fc21a0d
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T08:38:19Z
expires_at: 2026-07-20T08:48:19Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> 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: released candidate_head: 716fc21a0dd6521d71b6335f362f658654b5f69e target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T08:38:19Z expires_at: 2026-07-20T08:48:19Z blocker: manual-release
jcwalker3 added 1 commit 2026-07-20 04:09:01 -05:00
Remediates review 479/481 findings F1 and F2 on PR #767.

F1: issue #607 AC4 requires that an existing linked Gitea issue receives a
recurrence comment when Sentry events continue. That path did not exist.
This adds:

- incident_bridge.incident_recurred() — true only when event_count or
  last_seen genuinely advanced, compared against the pre-upsert link row, so
  an unchanged rescan stays silent and the exactly-once property holds.
- incident_bridge.build_recurrence_comment_body() — reuses the same
  redact_text path as build_gitea_issue_body, so redaction is not
  re-implemented.
- A comment_issue_fn hook on reconcile_incident, invoked only on
  OUTCOME_UPDATED with genuinely new events. The durable incident_links row
  is written first, so a comment failure degrades to "link updated, comment
  withheld" without corrupting the mapping or creating a duplicate issue.
- _incident_recurrence_comment_fn() in gitea_mcp_server.py, routing through
  the sanctioned gitea_create_issue_comment path named in issue #607. It
  returns None on dry runs and when the profile lacks gitea.issue.comment, so
  the AC8 dry-run default and disabled-mode safety hold.
- comment_issue_fn threading through sentry_incident_bridge.watchdog(), with
  the per-issue recurrence_comment outcome recorded in the scan result.

F2: observation_from_issue never populates a fingerprint, so the "deduped by
Sentry issue id and fingerprint" claim was unsupported. The
gitea_sentry_reconcile_issue and gitea_sentry_watchdog docstrings now state
the real dedupe basis: provider identity (provider, base URL, org, project,
Sentry issue id).

Tests: five new AC4 cases in tests/test_sentry_incident_bridge.py covering a
comment on the second scan, dry-run silence, no-new-events silence, link
durability when the comment fails, and comment redaction. Focused suite
43 passed (was 38).

Refs #607
Owner

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: bc968dd2e0
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T09:22:03Z
expires_at: 2026-07-20T09:32:03Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: bc968dd2e05a10529b08da2dea511d61a2404409 target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T09:22:03Z expires_at: 2026-07-20T09:32:03Z blocker: none
Owner

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: bc968dd2e0
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T09:24:01Z
expires_at: 2026-07-20T09:34:01Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: bc968dd2e05a10529b08da2dea511d61a2404409 target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T09:24:01Z expires_at: 2026-07-20T09:34:01Z blocker: none
sysadmin approved these changes 2026-07-20 04:27:15 -05:00
sysadmin left a comment
Owner

Canonical PR State

STATE:
PR #767 reviewed at pinned head bc968dd2e0 by a fresh independent reviewer. Review 481 findings F1, F2, and F3 are independently confirmed remediated at this head. One native APPROVE verdict is recorded at bc968dd2.

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:

Role: MERGER
Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools
Issue: #607
PR: #767
Approved head: bc968dd2e05a10529b08da2dea511d61a2404409
Base: master at edaeede250f46f361784061f025ab0e867172d2c

PR #767 carries one native APPROVE at head bc968dd2 from independent reviewer
sysadmin (prgs-reviewer), recorded after full independent verification of the
AC4 recurrence-comment implementation, the corrected dedupe-basis claim, and the
PR body record integrity.

Acquire the merger PR lease, pin head bc968dd2, and verify the approval is
visible, undismissed, and not stale at that exact head. Reviews 479 (cb6ae0ca)
and 481 (716fc21a) are both stale REQUEST_CHANGES verdicts against superseded
heads and do not apply to bc968dd2; confirm this through
gitea_get_pr_review_feedback rather than assuming it. Reassess checks, commits
behind base, conflicts, and mergeability immediately before merging.

Two full-suite failures are expected and are pre-existing #737 master drift:
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 reproduced on pristine master edaeede in this review session. Do not
attribute them to PR #767, do not repair #737 inside this merge, and do not
treat them as a merge blocker.

Merge only with explicit operator authorization and the exact confirmation
string. Do not review, do not amend the branch, do not delete branches or
worktrees, do not advance the control checkout, and do not restart the daemon.

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 bc968dd2 rather 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 at bc968dd2. 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 head bc968dd2, base edaeede, 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:
bc968dd2e0

REVIEW_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 edaeede in 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 master edaeede250, 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 master edaeede: 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: 716fc21a remains an ancestor of bc968dd2, so no force-push or history rewrite occurred. Published-content verification: all five paths at bc968dd2 are byte-identical by git blob SHA to the author's verified local integrated commit f0274bc4, including gitea_mcp_server.py at a12717166b, which the author could not blob-compare; git diff f0274bc4 bc968dd2 is 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 at edaeede250 and 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_incident computes recurrence = incident_recurred(existing, inc) inside the if existing: branch at incident_bridge.py:744, before db.upsert_incident_link at line 788 overwrites event_count and last_seen. The in-code comment states exactly this reason. incident_recurred returns true only when an integer event_count strictly advances, or when a non-empty last_seen differs from the stored value. The comment block at line 822 is additionally guarded on outcome == OUTCOME_UPDATED, so issue creation never emits a recurrence comment. test_recurrence_posts_a_comment_on_the_second_scan asserts 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 recurred branch and records {"posted": False, "reason": "no new provider events since the last sync"}. test_repeat_scan_without_new_events_posts_no_comment asserts OUTCOME_UPDATED with an empty comment list and posted false, which is the exactly-once property AC4 needs.

Dry-run and disabled modes produce no mutation. Two independent gates enforce this. reconcile_incident returns at incident_bridge.py:732 when apply is false, so the comment block at 822 is unreachable on a preview. Separately, _incident_recurrence_comment_fn returns None when apply is 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_comment asserts OUTCOME_PREVIEW with zero comments even against an already-linked recurrence.

Redaction is reused, not re-implemented. build_recurrence_comment_body routes the summary through the same redact_text call used by build_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 _clean sanitization. test_recurrence_comment_is_redacted injects token=abc123supersecret into 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_link completes 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_fn call is wrapped in try/except Exception, and the handler records a redacted reason on recurrence_comment without re-raising; execution continues to line 877 where db_mutated and success are set. A dict result reporting success: false is likewise recorded with its redacted reasons rather than raised. test_recurrence_comment_failure_keeps_the_link_durable injects a comment callable that raises and asserts OUTCOME_UPDATED, posted false, and that the link row still shows the advanced event_count of 42.

F2 (was non-blocking) — the dedupe claim now matches behavior

The gitea_sentry_reconcile_issue docstring 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 populate fingerprint, so fingerprint plays no part in its dedupe decisions. The gitea_sentry_watchdog docstring states the same basis, and additionally documents the AC4 comment route and its gitea.issue.comment requirement 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 at edaeede with 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_files call 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 bc968dd2 is empty, and all five paths match by blob SHA, including gitea_mcp_server.py at a12717166b. 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 edaeede cannot be regressed by this branch. All five tools gate gitea.read at 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_links on the #613 control-plane DB remains the sole durable mapping authority, and the new CommentIssueFn hook 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 bc968dd2

What is true now:

  • PR state: open, base master, head branch feat/issue-607-sentry-incident-bridge, mergeable true, 0 commits behind base
  • Head SHA at review time: bc968dd2e0, matching the pinned head, re-verified immediately before this submission
  • Ancestry: 716fc21a remains an ancestor of the current head; the publication was additive with no force-push and no history rewrite
  • Server-side decision state: review 479 at cb6ae0ca dismissed and stale; review 481 at 716fc21a undismissed but stale, applying to a superseded head; this review records one native APPROVE at the current head
  • Reviewer lease: session 21628-f893fba2f0c4, acquisition comment 13204, heartbeat comment 13211
  • Published content: all five paths at bc968dd2 byte-identical by blob SHA to the author's verified local integrated commit f0274bc4, gitea_mcp_server.py included
  • Validation at this head: focused 43 passed; shared substrate 94 passed; full suite 2 failed, 3780 passed, 6 skipped, 493 subtests passed; both failures reproduced on pristine master edaeede
  • Author issue lock on #607 remains live by design and is not a reviewer or merger blocker

What changed:

  • Server-side: the reviewer lease comments 13204 and 13211, and this APPROVE verdict. Nothing else.
  • Local: created one clean detached reviewer worktree branches/review-pr-767-bc968dd2 at 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 at edaeede250 and clean and was not used for task work.
  • Not changed: the author worktree branches/issue-607-sentry-incident-bridge, the prior reviewer worktree branches/review-pr-767, and the retained baseline worktrees branches/baseline-master-767-review and branches/baseline-master-issue-607 were all left untouched. The pre-existing #737 master drift was not repaired.

What is blocked:

  • Blocker classification: none
  • No code-defect, evidence-integrity, or process blocker was found at this head. This approval is not merge authorization; an independent merger must perform its own lease, approval-visibility, and mergeability reassessment.

Who/what acts next:

  • Next actor: merger
  • Required action: an independent prgs-merger acquires a merger PR lease on #767, pins head 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 authorization
  • Do not do: do not treat reviews 479 or 481 as applying to this head; do not re-review this PR without cause; do not amend, force-push, or rebase the branch; do not attribute the two pre-existing #737 failures to this PR; do not repair #737 inside this merge; do not delete the retained baseline worktrees or the author worktree; do not advance the control checkout or restart the daemon as part of the merge phase
  • Resume from: PR #767 at head bc968dd2e0, merger phase
## Canonical PR State STATE: PR #767 reviewed at pinned head bc968dd2e05a10529b08da2dea511d61a2404409 by a fresh independent reviewer. Review 481 findings F1, F2, and F3 are independently confirmed remediated at this head. One native APPROVE verdict is recorded at bc968dd2. 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 bc968dd2e05a10529b08da2dea511d61a2404409, reassesses checks, commits behind, conflicts, and mergeability, and merges through the sanctioned MCP merge operation. NEXT_PROMPT: ```text Role: MERGER Repo: prgs / Scaled-Tech-Consulting / Gitea-Tools Issue: #607 PR: #767 Approved head: bc968dd2e05a10529b08da2dea511d61a2404409 Base: master at edaeede250f46f361784061f025ab0e867172d2c PR #767 carries one native APPROVE at head bc968dd2 from independent reviewer sysadmin (prgs-reviewer), recorded after full independent verification of the AC4 recurrence-comment implementation, the corrected dedupe-basis claim, and the PR body record integrity. Acquire the merger PR lease, pin head bc968dd2, and verify the approval is visible, undismissed, and not stale at that exact head. Reviews 479 (cb6ae0ca) and 481 (716fc21a) are both stale REQUEST_CHANGES verdicts against superseded heads and do not apply to bc968dd2; confirm this through gitea_get_pr_review_feedback rather than assuming it. Reassess checks, commits behind base, conflicts, and mergeability immediately before merging. Two full-suite failures are expected and are pre-existing #737 master drift: 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 reproduced on pristine master edaeede in this review session. Do not attribute them to PR #767, do not repair #737 inside this merge, and do not treat them as a merge blocker. Merge only with explicit operator authorization and the exact confirmation string. Do not review, do not amend the branch, do not delete branches or worktrees, do not advance the control checkout, and do not restart the daemon. ``` 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 bc968dd2 rather 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 at bc968dd2. 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 head bc968dd2, base edaeede, 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: bc968dd2e05a10529b08da2dea511d61a2404409 REVIEW_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 edaeede in 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 master edaeede250f46f361784061f025ab0e867172d2c, 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 master edaeede: 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: 716fc21a remains an ancestor of bc968dd2, so no force-push or history rewrite occurred. Published-content verification: all five paths at bc968dd2 are byte-identical by git blob SHA to the author's verified local integrated commit f0274bc4, including gitea_mcp_server.py at a12717166b8e52327e4f8b8e2a92f004c772aaf1, which the author could not blob-compare; git diff f0274bc4 bc968dd2 is 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 == edaeede250f46f361784061f025ab0e867172d2c. Control checkout verified on master at edaeede250f4 and 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 bc968dd2e05a10529b08da2dea511d61a2404409. 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_incident` computes `recurrence = incident_recurred(existing, inc)` inside the `if existing:` branch at incident_bridge.py:744, before `db.upsert_incident_link` at line 788 overwrites `event_count` and `last_seen`. The in-code comment states exactly this reason. `incident_recurred` returns true only when an integer `event_count` strictly advances, or when a non-empty `last_seen` differs from the stored value. The comment block at line 822 is additionally guarded on `outcome == OUTCOME_UPDATED`, so issue creation never emits a recurrence comment. `test_recurrence_posts_a_comment_on_the_second_scan` asserts 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 recurred` branch and records `{"posted": False, "reason": "no new provider events since the last sync"}`. `test_repeat_scan_without_new_events_posts_no_comment` asserts OUTCOME_UPDATED with an empty comment list and `posted` false, which is the exactly-once property AC4 needs. Dry-run and disabled modes produce no mutation. Two independent gates enforce this. `reconcile_incident` returns at incident_bridge.py:732 when `apply` is false, so the comment block at 822 is unreachable on a preview. Separately, `_incident_recurrence_comment_fn` returns `None` when `apply` is 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_comment` asserts OUTCOME_PREVIEW with zero comments even against an already-linked recurrence. Redaction is reused, not re-implemented. `build_recurrence_comment_body` routes the summary through the same `redact_text` call used by `build_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 `_clean` sanitization. `test_recurrence_comment_is_redacted` injects `token=abc123supersecret` into 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_link` completes 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_fn` call is wrapped in `try/except Exception`, and the handler records a redacted reason on `recurrence_comment` without re-raising; execution continues to line 877 where `db_mutated` and `success` are set. A dict result reporting `success: false` is likewise recorded with its redacted reasons rather than raised. `test_recurrence_comment_failure_keeps_the_link_durable` injects a comment callable that raises and asserts OUTCOME_UPDATED, `posted` false, and that the link row still shows the advanced `event_count` of 42. ### F2 (was non-blocking) — the dedupe claim now matches behavior The `gitea_sentry_reconcile_issue` docstring 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 populate `fingerprint`, so fingerprint plays no part in its dedupe decisions. The `gitea_sentry_watchdog` docstring states the same basis, and additionally documents the AC4 comment route and its `gitea.issue.comment` requirement 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 at edaeede with 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_files` call 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 bc968dd2` is empty, and all five paths match by blob SHA, including gitea_mcp_server.py at a12717166b8e52327e4f8b8e2a92f004c772aaf1. 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 edaeede cannot be regressed by this branch. All five tools gate `gitea.read` at 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_links` on the #613 control-plane DB remains the sole durable mapping authority, and the new `CommentIssueFn` hook 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 bc968dd2 What is true now: - PR state: open, base master, head branch feat/issue-607-sentry-incident-bridge, mergeable true, 0 commits behind base - Head SHA at review time: bc968dd2e05a10529b08da2dea511d61a2404409, matching the pinned head, re-verified immediately before this submission - Ancestry: 716fc21a remains an ancestor of the current head; the publication was additive with no force-push and no history rewrite - Server-side decision state: review 479 at cb6ae0ca dismissed and stale; review 481 at 716fc21a undismissed but stale, applying to a superseded head; this review records one native APPROVE at the current head - Reviewer lease: session 21628-f893fba2f0c4, acquisition comment 13204, heartbeat comment 13211 - Published content: all five paths at bc968dd2 byte-identical by blob SHA to the author's verified local integrated commit f0274bc4, gitea_mcp_server.py included - Validation at this head: focused 43 passed; shared substrate 94 passed; full suite 2 failed, 3780 passed, 6 skipped, 493 subtests passed; both failures reproduced on pristine master edaeede - Author issue lock on #607 remains live by design and is not a reviewer or merger blocker What changed: - Server-side: the reviewer lease comments 13204 and 13211, and this APPROVE verdict. Nothing else. - Local: created one clean detached reviewer worktree branches/review-pr-767-bc968dd2 at 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 at edaeede250f4 and clean and was not used for task work. - Not changed: the author worktree branches/issue-607-sentry-incident-bridge, the prior reviewer worktree branches/review-pr-767, and the retained baseline worktrees branches/baseline-master-767-review and branches/baseline-master-issue-607 were all left untouched. The pre-existing #737 master drift was not repaired. What is blocked: - Blocker classification: none - No code-defect, evidence-integrity, or process blocker was found at this head. This approval is not merge authorization; an independent merger must perform its own lease, approval-visibility, and mergeability reassessment. Who/what acts next: - Next actor: merger - Required action: an independent prgs-merger acquires a merger PR lease on #767, pins head bc968dd2e05a10529b08da2dea511d61a2404409, 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 authorization - Do not do: do not treat reviews 479 or 481 as applying to this head; do not re-review this PR without cause; do not amend, force-push, or rebase the branch; do not attribute the two pre-existing #737 failures to this PR; do not repair #737 inside this merge; do not delete the retained baseline worktrees or the author worktree; do not advance the control checkout or restart the daemon as part of the merge phase - Resume from: PR #767 at head bc968dd2e05a10529b08da2dea511d61a2404409, merger phase
Owner

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: released
candidate_head: bc968dd2e0
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T09:32:54Z
expires_at: 2026-07-20T09:42:54Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> 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: released candidate_head: bc968dd2e05a10529b08da2dea511d61a2404409 target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T09:32:54Z expires_at: 2026-07-20T09:42:54Z blocker: manual-release
Owner

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: bc968dd2e0
target_branch: master
target_branch_sha: edaeede250
last_activity: 2026-07-20T09:44:00Z
expires_at: 2026-07-20T09:54:00Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: bc968dd2e05a10529b08da2dea511d61a2404409 target_branch: master target_branch_sha: edaeede250f46f361784061f025ab0e867172d2c last_activity: 2026-07-20T09:44:00Z expires_at: 2026-07-20T09:54:00Z blocker: none
sysadmin merged commit 059ee77c1f into master 2026-07-20 04:45:11 -05:00
Owner

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

Status: APPLIED

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

## Stale #332 review-decision lock cleanup (#594) Status: **APPLIED** - actor: `sysadmin` - profile: `prgs-merger` - timestamp: `2026-07-20T09:45:12.519570+00:00` - last terminal: `approve` on PR #767 - PR state: `closed` (merged=True) - merge_commit_sha: `059ee77c1fd483da385d6ccb4d842e808fb54630` - prior live_mutations_count: `2` - prior profile_identity: `prgs-reviewer` Manual deletion of session-state files is **not** the workflow. This path only clears a lock when the referenced PR is merged/closed.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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