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