This page is for the admin connecting your team’s GitLab projects to
Lettuce Cloud. If you’re standing up a self-hosted Lettuce install,
the one-time GitLab OAuth-application registration that backs this flow
is documented in
Self-hosted install §5.
Connect GitLab and add projects
Connect GitLab
On the GitLab tab, click Connect GitLab →. You’re redirected to
gitlab.com (or to your team’s self-hosted GitLab if your Lettuce
install is pointed at one).GitLab shows an authorize screen listing the three scopes Lettuce is
asking for —
api, read_repository, write_repository. Click
Authorize.GitLab redirects you back to Lettuce with a green toast.Pick a project
The Add-repo dialog now lists every GitLab project you have Developer
or higher access to (across all your groups), most-recently-active
first. Filter by
group/project substring, then click one.Lettuce:- Registers the project against your account.
- Auto-creates a push webhook on it so future commits trigger a re-index. (Idempotent — if the hook already exists from a previous add, we leave it alone.)
- Queues a clone + index job. Status moves from pending to indexing to ready — usually in a few seconds for small repos.
(Optional) Add more later
The dialog remembers your connection — re-open it any time to add
another project. Tokens refresh automatically; you only need to come
back here if you revoke Lettuce on GitLab or otherwise invalidate
the connection (changing your GitLab password, rotating tokens). In
that case the GitLab tab shows a Reconnect GitLab button.
What Lettuce asks for, and why
The Lettuce GitLab OAuth grant requests exactly three scopes:| Scope | Why |
|---|---|
api | OAuth-bound REST access — list your projects and register webhooks. |
read_repository | Git-over-HTTPS clone access. |
write_repository | Register the per-account push webhook on each project you add. |
write_repository
scope is a quirk of GitLab’s permission model — it’s needed only because
GitLab gates the webhook-registration API behind it. We do not push
commits.
How the security model works
- Per-account access + refresh tokens are stored against your Lettuce account. The access token is short-lived (default ~2h on gitlab.com); Lettuce refreshes proactively within 60 seconds of expiry whenever a call is made. If the refresh-token cycle fails (rare — you revoked Lettuce on GitLab, or the upstream rotated secrets), the UI surfaces a Reconnect GitLab prompt instead of returning a 500.
- The OAuth
stateparam on the callback is HMAC-signed and bound to the Lettuce account that started the connect flow, so a redirect can’t be replayed against a different account. - The webhook secret is per-account, generated automatically the
first time you add a repo, and reused across every project. GitLab
sends it back to us in the
X-Gitlab-Tokenheader on each delivery and Lettuce verifies it before doing any work. You can rotate the secret from Settings → Webhook in the Lettuce dashboard; rotation invalidates every existing GitLab webhook for your account and re-registers them with the new secret.
Disconnecting
Owners can disconnect the GitLab integration from the Lettuce side under Settings → Connected integrations (or via theDELETE /v1/integrations/gitlab API). Disconnecting:
- Discards the stored access and refresh tokens.
- Leaves all your existing repos in place — they keep working as long as they’re public. Private projects will fail their next re-clone until you reconnect or switch them to a paste-URL with credentials.
Troubleshooting
GitLab redirects me back with 'invalid or expired install state'
GitLab redirects me back with 'invalid or expired install state'
The signed
state param is older than 15 minutes. Restart the
install from the Add-repo dialog.GitLab redirects me back to 'gitlab_denied'
GitLab redirects me back to 'gitlab_denied'
You clicked Cancel (or denied one of the requested scopes) on the
GitLab authorize screen. Restart the install if it was an accident —
Lettuce needs all three scopes (
api, read_repository,
write_repository) to function.The GitLab tab shows 'Your GitLab session expired — Reconnect'
The GitLab tab shows 'Your GitLab session expired — Reconnect'
Lettuce’s refresh token was rejected. That happens when you revoked
Lettuce on GitLab, or GitLab itself rotated tokens (rare). Click
Reconnect GitLab to authorize afresh.
A project I just added is stuck on 'pending'
A project I just added is stuck on 'pending'
Check the Recent ingestion jobs section on the Repos page — failed
clones surface the underlying git error there. Most common cause:
your role on the project dropped below Developer — re-grant access
on GitLab.
My private project clones but later pushes don't trigger a refresh
My private project clones but later pushes don't trigger a refresh
The auto-webhook step may have failed if your role on the project is
below Maintainer (project webhooks require Maintainer or Owner on
some GitLab versions). Use Manual webhook setup on the Lettuce
repo page — it shows the per-account webhook URL + secret you can
paste into the project’s Settings → Webhooks manually. Set
Trigger to Push events and the Secret token to the value
Lettuce shows.
Self-hosted admin note. The end-user flow above only works once
the Lettuce GitLab OAuth application has been registered against the
GitLab instance you’re indexing. On Lettuce Cloud, the Lettuce team has
done this against gitlab.com for you. On a self-hosted install, your
admin registers the OAuth application once and points the worker /
api pods at it via the
GITLAB_APP_* and (for self-hosted GitLab)
GITLAB_HOST env vars — full runbook in
Self-hosted install §5.