> ## 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.

# GitLab

> Connect your GitLab projects to Lettuce — authorize via OAuth, pick projects, push webhooks wired automatically. Self-hosted GitLab supported.

Lettuce pulls projects directly from GitLab through OAuth. As the admin
connecting your team to Lettuce, you click *Connect GitLab*, authorize
the requested scopes on your GitLab account, pick the projects you want
indexed, and Lettuce takes care of cloning them plus wiring up push
webhooks so future commits trigger a re-index.

The manual "paste a git URL" flow keeps working alongside this — use it for
public projects hosted anywhere git speaks.

<Note>
  **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](/self-hosted/install#gitlab-self-hosted-let-21).
</Note>

## Connect GitLab and add projects

<Steps>
  <Step title="Open the Repos page">
    Sign in to Lettuce, go to **Repos**, click **+ Add repo**.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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:

    1. Registers the project against your account.
    2. 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.)
    3. Queues a clone + index job. Status moves from *pending* to
       *indexing* to *ready* — usually in a few seconds for small repos.

    No copy-pasting URLs, no fiddling with webhook secrets.
  </Step>

  <Step title="(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.
  </Step>
</Steps>

## 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.      |

Lettuce never asks for write access to your code. The `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 `state` param 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-Token` header 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 the `DELETE
/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.

To fully revoke access, also remove the authorization on the GitLab side:
**User Settings → Applications → Authorized applications → find Lettuce →
Revoke**.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The 'Connect GitLab' button is greyed out / missing">
    On Lettuce Cloud the GitLab integration is always available — if you
    see this state, refresh the page and re-open the Add-repo dialog. On
    a self-hosted Lettuce install, this means your admin hasn't
    finished the OAuth-application registration; see the self-hosted
    admin note below.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

<Note>
  **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](/self-hosted/install#gitlab-self-hosted-let-21).
</Note>
