Phase 1: Auto-Resolve Todos on MR Close/Merge
## Executive Summary
This proposal outlines Phase 1 implementation for automatically resolving stale todos when merge requests are closed or merged. The current system resolves todos only for the user who performs the action, leaving other assignees/reviewers with accumulating stale todos that clutter their todo dashboard.
**Key Goals:**
1. Auto-resolve todos for ALL affected users (not just the actor) when an MR closes/merges
2. Target specific "stale-prone" action types that become irrelevant after MR completion
3. Implement behind feature flag for safe rollout
4. Maintain performance with async processing
---
## Problem Statement
### Current Behavior
When a merge request is merged or closed, the system calls:
```ruby
# app/services/todo_service.rb:219-225
def resolve_todos_for_target(target, current_user)
attributes = attributes_for_target(target)
resolve_todos(pending_todos([current_user], attributes), current_user)
GraphqlTriggers.issuable_todo_updated(target)
end
```
**Issue:** This only resolves todos for `current_user` (the person who merged/closed the MR), but leaves pending todos for:
- Other assignees
- Reviewers who were requested but didn't complete review
- Approvers whose approval is no longer needed
- Anyone marked as APPROVAL_REQUIRED
### Impact
From issue comments and description:
- Todos accumulate over time, filling dashboards with "outdated litter"
- Users who primarily use GitLab for code review (not task management) find the Todo feature unusable
- Mentioned as important to commercial customers
- Estimated 6.3M stale todos on GitLab.com (from issue description)
---
## Proposed Solution
See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/229010+.
## Success Criteria
### Quantitative
- [ ] Zero new stale todos created for target action types
- [ ] Worker job completion rate > 99.9%
- [ ] P95 job duration < 500ms per MR
- [ ] Zero production incidents related to feature
- [ ] Database query time < 50ms per update
### Qualitative
- [ ] No user complaints about incorrect todo resolution
- [ ] Positive feedback from dogfooding team
- [ ] Code review approval from Code Review team
- [ ] Security review approval (if required)
---
## Risks & Mitigations
### Risk 1: Users Want to Keep Certain Todos
**Scenario:** A user wants to review an MR even after it's merged
**Mitigation:**
- Phase 1 only targets clearly stale action types (ASSIGNED, REVIEW_REQUESTED, APPROVAL_REQUIRED)
- Does NOT auto-resolve MENTIONED or DIRECTLY_ADDRESSED todos
- Users can still manually mark todos as "pending" if they want to track something
- Future: Add user preference to opt-out of auto-resolution
### Risk 2: Performance Impact on Large MRs
**Scenario:** MR with 50+ pending todos causes slow updates
**Mitigation:**
- Async processing prevents blocking merge operation
- Batch update uses single SQL statement regardless of todo count
- Monitored rollout catches issues early
- Can add rate limiting or batch size limits if needed
### Risk 3: EE Action Types Unavailable in CE
**Scenario:** `APPROVAL_REQUIRED` and `ADDED_APPROVER` don't exist in CE
**Mitigation:**
```ruby
stale_action_types = [
Todo::ASSIGNED,
Todo::REVIEW_REQUESTED
]
# Only include EE action types if they exist
if defined?(Todo::APPROVAL_REQUIRED)
stale_action_types << Todo::APPROVAL_REQUIRED
stale_action_types << Todo::ADDED_APPROVER
end
```
### Risk 4: Race Condition with Reopened MRs
**Scenario:** MR is closed, todos resolved, then MR is reopened
**Mitigation:**
- Todos stay resolved (correct behavior - they were already actioned)
- New assignment/review request creates fresh todos
- Users can manually re-create todos if needed
- Document expected behavior in feature documentation
---
## Future Enhancements (Out of Scope for Phase 1)
### User Preferences
Allow users to customize auto-resolution behavior:
```ruby
# app/models/user_preference.rb
attribute :auto_resolve_mr_todos, :boolean, default: true
attribute :auto_resolve_issue_todos, :boolean, default: false
```
### Phase 2: Historical Data Cleanup
Background migration to clean up existing 6.3M stale todos (separate proposal)
### Extended Action Types
Consider adding:
- `UNMERGEABLE` - when MR becomes mergeable or is closed
- `BUILD_FAILED` - when build succeeds or MR is closed
### Issue Todo Resolution
Apply similar logic when issues are closed
---
## Questions for Discussion
1. **Should we resolve todos for the MR author?**
- Author already gets their todos resolved when they close/merge
- But if another user closes/merges, should author's todos also resolve?
- Recommendation: Yes, for consistency
2. **Should this apply to draft MRs?**
- Draft MRs can still be closed/merged
- Recommendation: Yes, same behavior
3. **Should we add a GraphQL mutation to manually trigger this?**
- Useful for testing and edge cases
- Recommendation: Not for Phase 1, can add later if needed
4. **Should we send notifications about auto-resolved todos?**
- Could be noisy for users with many todos
- Recommendation: No notifications, silent resolution
5. **What about MRs that are closed then reopened?**
- Todos stay resolved (they were addressed)
- New assignment/review creates fresh todos
- Recommendation: Document this behavior, no special handling
---
## References
- **Issue:** https://gitlab.com/gitlab-org/gitlab/-/issues/20637
- **Related Issue:** https://gitlab.com/gitlab-org/gitlab-ce/issues/30521
- **Todo Model:** [app/models/todo.rb](app/models/todo.rb)
- **TodoService:** [app/services/todo_service.rb](app/services/todo_service.rb)
- **MR Close Service:** [app/services/merge_requests/close_service.rb](app/services/merge_requests/close_service.rb)
- **MR Post-Merge Service:** [app/services/merge_requests/post_merge_service.rb](app/services/merge_requests/post_merge_service.rb)
epic