Require a human sign-off, without paid approval rules.
Every merge should have a person's Approve or 👍 on it, and when agents open merge requests under the owner's account, that rule has to hold. This page explains the need, the pipeline job that enforces it, the gap GitLab leaves, and the small webhook receiver that closes it.
pipeline require-approval -> red until someone signs off person Approve or 👍 -> GitLab does not re-run the pipeline webhook approval event -> Kelpie retries that one job merge "Pipelines must succeed" unlocks the button
The need. With AI agents opening merge requests, a merge needs a person who has looked at it. The obvious tool is GitLab's approval rules, and two things rule them out here.
Approval rules need a paid tier. uBixCore's standard records that the rules are a paid-tier feature, so a team on the free edition cannot rely on them to block a merge.
They deadlock when every MR has the same author. An author cannot approve their own merge request. When all work, human or agent, is opened under the owner's account, no one else can approve, so nothing would ever merge.
So the rule lives in the pipeline instead. A job named require-approval runs uBixCore's ci:requireApproval check, and the project setting Pipelines must succeed turns that job's result into a blocked merge button. Without that setting the job is only a red mark.
It passes when the merge request has an Approve or a 👍 from anyone but its author, or from its author when the author is listed in MERGE_APPROVAL_OWNERS. A self sign-off is logged as SELF SIGN-OFF, so a pushed-through merge is recorded rather than invisible.
Two limits are stated plainly in the standard. The job fails open: with no token, or GitLab unreachable, it passes with an UNVERIFIED line instead of holding every merge hostage to an outage. And it is a guard against merging in haste, not a security boundary: a click costs nothing to make unread. Agents are told never to approve or 👍 their own work, because their API access would let them.
GitLab does not re-run a pipeline when someone approves. The job is red when the merge request opens, the person approves, and it stays red until someone retries it by hand. That click defeats the point of automating the gate.
uBixKelpie hosts a GitLab webhook receiver for this. Its endpoint is POST /gitlab/webhook, and it does five things:
- Refuses a wrong or missing
X-Gitlab-Tokenwith 401. Everything else is answered 200, so GitLab never disables the hook. - Accepts a merge request
approval/approved/unapproval/unapprovedevent, and athumbsupemoji awarded or revoked on a merge request. - Reads the merge request's head pipeline and finds the newest job named in
APPROVAL_JOB_NAMES(defaultrequire-approval,require-approval-mr). - Retries that one job, or plays it if it is manual. A job already queued or running is left alone, and an already passed job is left alone unless the sign-off was withdrawn.
- Logs a GitLab failure and still answers 200.
It never creates a pipeline: a new one would re-run every check, including the AI review, on each click.
Kelpie never approves. The token it holds could approve or award an emoji, so token scope is no protection. The guard is in code: the approval service is given an HTTP client that refuses everything except reading a merge request, reading a pipeline's jobs, and retrying or playing a job. A test asserts the refusals.
Per project, once:
- A last-stage merge-request job named
require-approvalthat runsphp bin/ubix ci:requireApproval, withMERGE_APPROVAL_OWNERSset if an owner may sign off their own work. It needs the masked CI variableAPPROVAL_GITLAB_TOKEN(Reporter,api). Without it the job passes as UNVERIFIED, so the gate enforces nothing. - A bot token for the receiver, Developer role with
apiscope, inGITLAB_PROJECT_TOKENS(or one group token inGITLAB_API_TOKEN). Create it as a project access token, so the audit trail shows a bot retried the job. - A webhook in Settings → Webhooks, pointing at Kelpie's
/gitlab/webhook, with the secret token fromGITLAB_WEBHOOK_SECRET, the triggers Merge request events and Emoji events, and SSL verification on. - Turn on "Pipelines must succeed" in the project's merge request settings.
Group webhooks, which would cover every project with one entry, are a paid-tier GitLab feature, so on a free edition the hook is added per project.
The receiver logic is not Kelpie's own: it is uBixCore's ApprovalWebhookService, the same class behind uBixOps, which Kelpie calls from its own endpoint. A 👍 starting the job needs uBixCore v0.46.1 or later. Where uBixOps is deployed and the project's hook points at it, the effect is the same; do not point one project's hook at both.
This is a guard for a team that shares one account, not a replacement for review. Pair it with a code review gate, as in the AI agents chapter.
Written against uBixCore v0.46.1 and uBixKelpie v0.1.0-beta.1.