← Guides
uBixCore · v0.46.1 · uBixKelpie · v0.1.0-beta.1

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.

Synopsis
the loop
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
Why this exists

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.

What the check does

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.

The gap, and how Kelpie closes it

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:

  1. Refuses a wrong or missing X-Gitlab-Token with 401. Everything else is answered 200, so GitLab never disables the hook.
  2. Accepts a merge request approval / approved / unapproval / unapproved event, and a thumbsup emoji awarded or revoked on a merge request.
  3. Reads the merge request's head pipeline and finds the newest job named in APPROVAL_JOB_NAMES (default require-approval, require-approval-mr).
  4. 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.
  5. 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.

Wiring a project

Per project, once:

  1. A last-stage merge-request job named require-approval that runs php bin/ubix ci:requireApproval, with MERGE_APPROVAL_OWNERS set if an owner may sign off their own work. It needs the masked CI variable APPROVAL_GITLAB_TOKEN (Reporter, api). Without it the job passes as UNVERIFIED, so the gate enforces nothing.
  2. A bot token for the receiver, Developer role with api scope, in GITLAB_PROJECT_TOKENS (or one group token in GITLAB_API_TOKEN). Create it as a project access token, so the audit trail shows a bot retried the job.
  3. A webhook in Settings → Webhooks, pointing at Kelpie's /gitlab/webhook, with the secret token from GITLAB_WEBHOOK_SECRET, the triggers Merge request events and Emoji events, and SSL verification on.
  4. 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.

Notes

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.