Lettuce can pull repos directly from GitHub through a GitHub App that your operator (the team running the Lettuce backend) installs once on their GitHub organisation. Users in your account then install that App on their own GitHub account/org, pick repos from a list, and Lettuce indexes them plus wires up auto-refresh webhooks — no copy-pasting URLs or webhook secrets. The manual “paste a git URL” flow keeps working alongside this — use it for public repos 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.
There are two audiences here. Operators (you self-host Lettuce) do the
one-time GitHub App setup. Users (you sign in to Lettuce on the web)
just click Add via GitHub and pick repos.
For operators — one-time setup
You only do this once per Lettuce deployment.Create a new GitHub App
Open https://github.com/settings/apps/new
(or, for an org-owned App,
https://github.com/organizations/<your-org>/settings/apps/new).Fill in:- GitHub App name:
Lettuce(must be globally unique — tryLettuce - acmeifLettuceis taken). Whatever you choose, the slug in the URL of the App’s settings page is what you’ll paste intoGITHUB_APP_NAMEbelow. - Homepage URL: your Lettuce public URL, e.g.
https://diet.uselettuce.dev. - Callback URL:
https://<your-lettuce-host>/v1/integrations/github/callback— Lettuce verifies an HMAC-signedstateparam here, so the URL must be exact. Tick Redirect on update. - Setup URL (optional): same as Callback URL. Tick Redirect on update.
- Webhook:
- Set Webhook URL to anything (e.g.
https://example.com/unused) and untick Active. Lettuce wires webhooks per-repo at add-time using a per-account secret; the App-level webhook is intentionally unused. - You can also enable Active and point it at the same URL — Lettuce only reads repo-scoped deliveries so the App-level hook is harmless.
- Set Webhook URL to anything (e.g.
Permissions and events
Under Repository permissions:
- Contents: Read-only (needed to clone)
- Metadata: Read-only (mandatory baseline)
- Webhooks: Read and write (so Lettuce can install a hook per repo)
push directly.Where can this GitHub App be installed?
Choose Any account so your users can install it on their personal
accounts or their orgs. Leave it as Only on this account if you
only want internal users.
Generate a private key
Scroll to Private keys → Generate a private key. A
.pem file
downloads. Keep this file safe — Lettuce signs short-lived
installation tokens with it on every clone.Note the App ID and Client ID, generate a Client secret
On the App settings page:
- Copy the App ID (numeric, near the top).
- Copy the Client ID.
- Click Generate a new client secret and copy the value — you can only see it once.
Set env vars on the Lettuce backend
GITHUB_APP_PRIVATE_KEYis the full PEM contents, newlines and all. Most platforms accept multiline env vars; on Render set it as a secret file or use\nescapes in a single-line value.GITHUB_APP_NAMEis the App’s slug, not its display name. If your App’s URL ishttps://github.com/apps/lettuce-prod, the slug islettuce-prod.CODEWAZE_PUBLIC_URLmust already be set on the backend to a URL that GitHub can reach — the Callback URL above is built from it.
How the security model works
- The App’s private key lives only in
GITHUB_APP_PRIVATE_KEYon the backend. It is never written to the database. - When Lettuce needs to clone or call the GitHub API for an account, it mints a fresh installation access token (≈1 hour TTL) from the App JWT. The token is held in memory for the duration of the request and discarded.
- The OAuth-style
stateparam on the install callback is an HMAC of(account_id, provider, timestamp, nonce)signed with the backend’s admin token. This binds the install to the account that started it — even though GitHub’s redirect carries no session cookie. - The webhook secret is per-account, stored in
accounts.webhook_secret, and reused across every repo the account adds. Rotating it viaPOST /v1/account/webhook/rotateinvalidates every existing GitHub webhook for that account.
For users — connect GitHub and add repos
This is what users see in the Lettuce UI once the operator has set up the App above.Install the Lettuce GitHub App
On the GitHub tab, click Install GitHub App →. You’re redirected
to GitHub.Choose where to install the App:
- Your personal account, or
- An organisation you have admin rights on.
Pick a repo
You land back on Repos with a green toast — “GitHub connected”.
The Add-repo dialog now lists every repo the install can see; filter by
owner/repo substring, then click one.Lettuce:- Registers the repo against your account.
- Auto-creates a webhook on it (push events) so future commits trigger a re-index.
- Queues a clone + index job. Status moves from pending to indexing to ready — usually in a few seconds for small repos.
Disconnecting
Owners can disconnect the GitHub install from the Lettuce side atDELETE /v1/integrations/github (a UI button is on the roadmap).
Disconnecting:
- Removes the
provider_installationsrow. - 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 'Add via GitHub' button is greyed out / missing
The 'Add via GitHub' button is greyed out / missing
GitHub redirects me back with 'invalid or expired install state'
GitHub redirects me back with 'invalid or expired install state'
The signed
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.A repo I just added is stuck on 'pending'
A repo 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. Common cases: the user
who installed the App didn’t grant access to that repo (re-configure
the install on GitHub), or the operator’s backend can’t reach
github.com.My private repo clones but later pushes don't trigger a refresh
My private repo clones but later pushes don't trigger a refresh
The auto-webhook step may have failed if the installer didn’t have
Webhooks: read-and-write on the repo. Use Automatic refresh on the
Repos page — it shows the per-account webhook URL + secret you can
paste into the repo’s Settings → Webhooks manually.