by Winch Labs

Reviews

Connecting GitLab

This has now run against a live GitLab project, end to end: a merge request on gitlab.com triggered a group webhook, the gate reviewed it, a note was posted with three findings, and the evidence links resolved into the project at the reviewed commit.

It is still younger than the GitHub path. What has been exercised and what has not is listed below rather than summarised as a single word.

Exercised on a live project: per-organisation credentials, claiming a group, group webhook delivery and authentication, the deterministic gate, posting a merge-request note, and evidence links resolving with GitLab’s /-/blob/ path shape.

Not yet exercised on a live project: self-managed GitLab (only gitlab.com so far), the Fix and Forge write paths, and anything at sustained volume.

If you are evaluating Deltz on GitLab, we would still like to hear from you: [email protected].

Configuration

Variable Value
GARBOARD_GIT_PROVIDER gitlab
GARBOARD_GITLAB_URL Your GitLab base URL
GARBOARD_GITLAB_TOKEN A token with api scope, at least Developer. Prefer a group access token — see below.
GARBOARD_GITLAB_WEBHOOK_SECRET Required — see below
GARBOARD_GITLAB_GROUP Optional. Scopes the instance to one group.

The webhook secret is fail-closed. Leave GARBOARD_GITLAB_WEBHOOK_SECRET unset and every delivery is refused, with a warning logged at startup. There is no unauthenticated mode: an unsigned webhook endpoint on a review gate would let anyone submit anything for review.

One instance serves both hosts. Credentials are stored per organisation, so a single deployment reviews a GitHub tenant and a GitLab tenant at the same time. Earlier builds chose one provider at startup for the whole process — that is no longer how it works, and the environment variables below are now the fallback for a single-tenant self-hosted install rather than the only way in. An admin connects GitLab from Settings → Repositories instead.

Connecting from the UI

Settings → Repositories → GitLab. Enter your instance URL, an access token and a webhook secret you choose. They are stored encrypted and scoped to your workspace, so another workspace on the same instance can be on GitHub, or on a different GitLab entirely.

The URL is checked before anything is stored: it must be a publicly routable https address, so a loopback, private-network or cloud-metadata address is refused rather than fetched.

Use a group access token, not a personal one

This credential is long-lived — unlike a GitHub App, which mints a fresh token every hour and expires it. So whose identity it carries is the question that matters most.

A group access token is a bot identity that belongs to the group. A personal access token belongs to a person, and stops working the day they leave — taking your reviews with it, at the least convenient possible moment.

Both work. Only one of them survives someone changing jobs.

The secrets are write-only. A stored token reads as “stored” and is never shown back, in whole or in part — a masked value would leak its length and its tail, which is worth less than it looks. Rotating means entering a new one.

The environment variables above still work, and apply when no workspace connection is stored — which is how a single-tenant self-hosted install is meant to run.

Then claim a group.

Settings → Repositories → Connect a GitLab group lists every root namespace the instance token can reach, with each one’s state. Claiming binds that namespace to your workspace and starts a scan of its projects.

Two things worth knowing before you click:

  • A claimed group is bound permanently. It is never re-pointed, not even to tell you which workspace holds it. Claim the one you mean.
  • A group another workspace has claimed is not listed at all. It is omitted rather than shown as unavailable, because even its name would say who else is a customer.

On an instance that is not on GitLab, the panel says so and names the variables an operator has to set, rather than showing an empty list or an error.

A real behavioural difference

GitLab commit statuses have no neutral state.

On GitHub, an advisory-only review posts a neutral check — visibly “we looked, nothing blocking”. GitLab does not have that colour. So on GitLab an advisory posts a success status with the description prefixed advisory ·.

The information survives; the traffic light does not. If your team reads status colour rather than status text, an advisory on GitLab will look like a clean pass. Worth knowing before you wire it into a merge policy.

Tenant binding

Installations bind by root namespace. Every table is organisation-scoped, and the webhook path passes the installation’s organisation explicitly rather than falling back to a default — otherwise one tenant’s muted rules would stop applying to their own merge requests.