Quick action preview inconsistently omits commands due to transient MR state
<!--IssueSummary start--> <details> <summary> Everyone can contribute. [Help move this issue forward](https://handbook.gitlab.com/handbook/marketing/developer-relations/contributor-success/community-contributors-workflows/#contributor-links) while earning points, leveling up and collecting rewards. </summary> - [Label this issue](https://contributors.gitlab.com/manage-issue?action=label&projectId=278964&issueIid=590706) </details> <!--IssueSummary end--> ## Summary The quick action preview in the MR comment box inconsistently omits commands when multiple quick actions are used together (e.g. `/approve`, `/run_pipeline`, `/merge`). Sometimes all three appear in the preview, sometimes only a subset (e.g. 2 out of 3) is shown. Which command is omitted varies — it is not always the same one. ## Steps to reproduce 1. Open a merge request 2. In the comment box, type: ``` /approve /run_pipeline /merge ``` 3. Observe the preview — it may show all three commands, or only a subset 4. Repeat on different MRs — the behavior is inconsistent ## Observed behavior - In some MRs, all three commands appear in the preview - In others, only 2 of 3 appear (e.g. `/run_pipeline` and `/merge` show but `/approve` does not) - The subset shown can differ across MRs in the same project and across projects See the original report in https://gitlab.com/gitlab-org/gitlab/-/work_items/210538#note_3060722435 for screenshots and specific examples. ## Expected behavior The preview should consistently show all applicable quick actions. ## Root cause analysis The preview is generated via the `preview_markdown` endpoint, which calls `QuickActions::InterpretService#explain`. For each command, `CommandDefinition#explain` (`lib/gitlab/quick_actions/command_definition.rb:45-46`) calls `available?`, which evaluates the command's `condition` block against the MR's current state. If `available?` returns `false`, the command is **silently omitted** from the preview. Each command's condition (`lib/gitlab/quick_actions/merge_request_actions.rb`) performs stateful checks: - **`/approve`** (line 260): calls `eligible_for_approval_by?` — in EE this loads approval rules, associated users/groups, and checks the approvals table via `ApprovalState` - **`/run_pipeline`** (line 516): calls `CreatePipelineService#allowed?` — checks pipeline existence and user permissions - **`/merge`** (line 47): calls `merge_orchestration_service.can_merge?` — cascades into `mergeable?` → `check_mergeability`, which acquires an exclusive lease and can trigger a Gitaly `merge-to-ref` operation All of these conditions are sensitive to the MR's transient state (e.g. `merge_status` being `unchecked` after a push, exclusive lease contention, approval rules loading). When any condition returns `false` due to timing, the corresponding command silently disappears from the preview. ## Relevant files - `lib/gitlab/quick_actions/command_definition.rb` — `available?` / `explain` gating - `lib/gitlab/quick_actions/merge_request_actions.rb` — condition blocks for `/merge`, `/approve`, `/run_pipeline` - `app/services/preview_markdown_service.rb` — preview service - `app/services/quick_actions/interpret_service.rb` — `explain` method - `app/services/merge_requests/merge_orchestration_service.rb` — `can_merge?` - `app/services/merge_requests/mergeability_check_service.rb` — exclusive lease logic
issue