Skip to content

feat: recover stalled MRs via emoji-triggered API refresh#45

Merged
babs merged 1 commit into
masterfrom
feat/recover-missed-mr-close
May 5, 2026
Merged

feat: recover stalled MRs via emoji-triggered API refresh#45
babs merged 1 commit into
masterfrom
feat/recover-missed-mr-close

Conversation

@babs

@babs babs commented May 5, 2026

Copy link
Copy Markdown
Member

Summary

Missed merge/close webhooks (delivery failures, or partial rollbacks where state=\"merged\" commits but msg_to_delete/refs cleanup doesn't) leave Teams cards frozen on the open-state view. Emoji events on the MR now trigger a recovery probe: cleanup tick fetches current state from the GitLab API; on confirmed terminal state, the leftover refs are PATCHed to the merged card and queued for deletion. Idempotent — no-op if refs already cleaned.

Key changes

  • DBHelper.refresh_mr_payload_from_api: rewrites stored payload from API response. Synthesizes action="merge"/"close"/"reopen" only on terminal/reopen transitions because cards/render.py keys icon selection off action, not state.
  • Normalize API ISO 8601 updated_at to webhook format (YYYY-MM-DD HH:MM:SS UTC) so the existing fromisoformat parser keeps a single canonical column shape.
  • Defensive: assume UTC for naive datetimes (host-TZ skew protection); tolerate unparseable timestamps without sticking the pending_mr_refresh row.
  • New api-refresh-close cleanup branch fires only when the API was actually called and confirmed terminal state (api_refreshed AND is_closing_state); idempotent via update_all_messages_transactional's no-op on empty locked_messages.
  • Distinguish API-unavailable (warning) from API-confirmed-open in logs for triage.

Rate-limit relies on the existing pending_mr_refresh debounce (PRIMARY KEY on merge_request_ref_id, ON CONFLICT extending process_after) — multiple emoji on the same MR collapse to one API call per debounce window.

Test plan

  • 15 new unit tests in tests/test_refresh_mr_payload.py covering state→action mappings, head_pipeline absent/present/null, ISO normalization (Z, offset, naive), unparseable timestamps, draft sync, no-token short-circuit, and HTTP-error handling.
  • pytest 230/230 (full suite incl. e2e against testcontainer).
  • pre-commit run --all-files clean.

Missed merge/close webhooks (delivery failures, or partial rollbacks
where state="merged" commits but msg_to_delete/refs cleanup doesn't)
leave Teams cards frozen on the open-state view. Emoji events on the
MR now trigger a recovery probe: cleanup tick fetches current state
from the GitLab API; on confirmed terminal state, the leftover refs
are PATCHed to the merged card and queued for deletion. Idempotent --
no-op if refs already cleaned.

- Synthesize action from state on terminal/reopen transitions
  (cards/render.py keys icon off action, not state).
- Normalize API ISO 8601 updated_at to webhook format so the existing
  fromisoformat parser keeps a single canonical shape.
- Assume UTC for naive datetimes (host-TZ skew protection); tolerate
  unparseable timestamps without sticking the pending refresh.
- Distinguish API-unavailable (warning) from API-confirmed-open in
  logs for triage.

Rate-limit relies on the existing pending_mr_refresh debounce.
@babs babs merged commit 4e3ff68 into master May 5, 2026
1 check passed
@babs babs deleted the feat/recover-missed-mr-close branch May 5, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant