Lettuce can pull projects directly from GitLab through a GitLab OAuth application that your operator (the team running the Lettuce backend) registers once. Users in your account then click Connect GitLab, authorize Lettuce on their personal account, pick projects from a list, and Lettuce indexes them plus wires up push webhooks — no copy-pasting URLs or webhook secrets. The manual “paste a git URL” flow keeps working alongside this — use it for public projects hosted anywhere git speaks.Documentation Index
Fetch the complete documentation index at: https://recipe.uselettuce.dev/llms.txt
Use this file to discover all available pages before exploring further.
For operators — one-time setup
You only do this once per Lettuce deployment. The same flow works for gitlab.com or any self-hosted GitLab (CE or EE).Open the right Applications page
Fill in the application form
- Name:
Lettuce(anything you like — users see this on the authorize screen). - Redirect URI:
https://<your-lettuce-host>/v1/integrations/gitlab/callback— GitLab is strict about exact-match. No trailing slash, no path variants. The host must equalCODEWAZE_PUBLIC_URLon the backend. - Confidential: Yes. Lettuce holds the client secret on a server, so the public-client mode would just weaken the flow.
- Expire access tokens: leave at the default (on). Lettuce refreshes tokens automatically using the refresh token GitLab issues.
- Scopes: tick exactly these three:
api— read-only OAuth-bound REST access (used to list projects and register webhooks).read_repository— git-over-HTTPS clone access.write_repository— needed to register the per-account push webhook on each added project.
Save and copy the credentials
Set env vars on the Lettuce backend
CODEWAZE_PUBLIC_URLmust already be set on the backend to a URL GitLab can reach — the Redirect URI above is built from it.GITLAB_BASE_URLdefaults tohttps://gitlab.com. Self-hosted operators point it at their own host; OAuth, REST, and clone URLs all derive from this one value.
How the security model works
- The OAuth client secret lives only in
GITLAB_APP_CLIENT_SECRETon the backend. It is never written to the database and never sent to the browser. - Per-account tokens (access + refresh) are stored on the
provider_installationsrow. 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 — the user revoked Lettuce on GitLab, or the secret rotated), the install is flaggedmeta.needs_reauth = trueand the UI prompts the user to reconnect instead of returning a 500. - The OAuth
stateparam on the callback is an HMAC of(account_id, provider, timestamp, nonce)signed with the backend’s admin token — same construction as the GitHub vertical. The browser comes back from gitlab.com with no Lettuce session cookie; the HMAC rebinds the install to the account that started it. - The webhook secret is per-account, stored in
accounts.webhook_secret, and reused across every repo. GitLab sends it back to us in theX-Gitlab-Tokenheader on each delivery; Lettuce compares it withhmac.compare_digest. (GitLab does not HMAC the body the way GitHub does, so a literal token compare is the strongest available check.) Rotating the secret viaPOST /v1/account/webhook/rotateinvalidates every existing GitLab webhook for that account.
For users — connect GitLab and add projects
This is what users see in the Lettuce UI once the operator has set up the OAuth app above.Connect GitLab
GITLAB_BASE_URL).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
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) Self-hosted GitLab note
GITLAB_BASE_URL, the Connect GitLab button
takes you to that host, not gitlab.com. Everything else (scope screen,
project picker, webhook auto-register) is identical.(Optional) Add more later
Disconnecting
Owners can disconnect the GitLab integration from the Lettuce side atDELETE /v1/integrations/gitlab (a UI button is on the roadmap).
Disconnecting:
- Removes the
provider_installationsrow and discards the stored access and refresh tokens. - Leaves all your existing repos in place — they keep working as long as they’re public. Private repos will fail their next re-clone until you reconnect or switch them to a paste-URL with credentials.
Troubleshooting
The 'Connect GitLab' button is greyed out / missing
The 'Connect GitLab' button is greyed out / missing
GitLab redirects me back with 'invalid or expired install state'
GitLab redirects me back with 'invalid or expired install state'
state param is older than 15 minutes, or the backend’s
admin token has rotated since you started the install. Restart the
install from the Add-repo dialog.GitLab redirects me back to 'gitlab_denied'
GitLab redirects me back to 'gitlab_denied'
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'
A project I just added is stuck on 'pending'
A project I just added is stuck on 'pending'
My private project clones but later pushes don't trigger a refresh
My private project clones but later pushes don't trigger a refresh
Redirect URI mismatch on the GitLab side
Redirect URI mismatch on the GitLab side
http vs https, or a different port. The expected value is
<CODEWAZE_PUBLIC_URL>/v1/integrations/gitlab/callback with no
trailing slash.