prepared_at is documented as waiting for preparation steps that are not awaited
Problem
The API documentation states that prepared_at populates only after all five preparation steps complete:
- Create the diff.
- Create the pipelines.
- Check mergeability.
- Link all Git LFS objects.
- Send notifications.
Two of those are scheduled asynchronously and nothing waits for them before prepared_at is set, so the field does not mean what the documentation promises:
- Create the pipelines -
MergeRequests::AfterCreateService#prepare_for_mergeabilitycallscreate_pipeline_for(merge_request, current_user, async: true), which enqueues the work. - Check mergeability - the same method calls
merge_request.check_mergeability(async: true), which enqueuesMergeRequestMergeabilityCheckWorker.
mark_merge_request_as_prepared then runs in the same job, without waiting for either. On a quiet instance the mergeability check often finishes after prepared_at is already set, and it can finish minutes later when Sidekiq is busy.
Proposal
Correct the documentation rather than the ordering. Split the list into the steps that are guaranteed to have finished and the steps that are only scheduled, so API consumers can rely on what prepared_at actually tells them.
Alternative considered, and why not
Delaying prepared_at until the async steps finish looks like the "correct" fix but breaks two things:
- It is the retry guard for
NewMergeRequestWorker. That worker returns early whenissuable&.prepared?is true, anddeduplicate :until_executeddoes not cover Sidekiq retries because retries skip client middleware. Movingprepared_atlater means a retried job re-runs the whole ofAfterCreateService: duplicate notifications, todos, cross-references and webhooks. A replacement durable marker would be needed first. - It changes the merge request create webhook payload.
prepared_atis inGitlab::HookData::MergeRequestBuilder.safe_hook_attributesandexecute_hooksruns aftermark_merge_request_as_prepared, so consumers would start receivingprepared_at: nullon create.
Both are worth discussing on their own merits, but neither should be a prerequisite for the documentation matching the behaviour.
Context
Came up in review of !242698 (merged), which adds a Duo Agent Platform trigger that fires once the diff is built and code-owner approval rules are synced against it. While reviewing when that event should fire, we found the documented meaning of prepared_at and its implementation disagree.