Conversation Pull Requests
Show the GitHub pull request, size and CI status for chats that run code in an attached workspace.
When a chat runs code in an attached workspace, the worker reports the git branch and commit it is on. LibreChat uses that branch to find the matching GitHub pull request, with a token held on the server, and shows it in two places:
- Chat header: a chip with the pull request icon and a CI dot. Hovering or focusing it opens a card.
- Sidebar conversation list: the same icon and CI dot at the end of the row. The row keeps the conversation's own title; the pull request title appears only in the card.
Requirements
All of these must be true for a chat to show a pull request:
- LibreChat has a GitHub token and a repository scope. See Setup.
- The chat uses an attached code workspace (a code worker), not a plain chat.
- The worker reports its branch. This needs a worker built from
LibreChat-AI/code-interpreterPR #311 or later, andCODEAPI_BRIDGE_LANE_GIT=trueset on the Code API. See Code Interpreter.
If a requirement is missing, chats simply show no pull request and no error is shown to the user. The exception is a token that is set but whose environment variable is empty or missing: lookups then fail with NOT_CONFIGURED, and sidebar rows show the warning icon with a retry button.
Setup
1. Create a token
Use a fine-grained GitHub token limited to the repositories you want to show, with read-only permissions:
- Pull requests
- Checks
- Contents
Permission failure modes
Without Checks, every matched pull request fails to load. Without Contents, a chat whose branch has moved on since its last command fails to load.
2. Give LibreChat the token
Pending: not yet released
Environment variable fallback is part of the default-on change (LibreChat-AI/LibreChat#16876).
LibreChat looks for a token in this order:
token: "${MY_VAR}"inlibrechat.yaml. This is an environment variable reference; the YAML never holds the token itself.- If
tokenis not set, the first of these environment variables that is set:GITHUB_PULL_REQUEST_TOKEN,GITHUB_TOKEN,GH_TOKEN. - No token anywhere: the feature stays dormant.
A token that is set but does not resolve (the variable is empty or missing) fails with NOT_CONFIGURED. It does not fall back to the three variables above.
GITHUB_TOKEN is a common name
If your deployment already uses GITHUB_TOKEN for something else, the default-on behavior starts
using it here as soon as a repository scope is set. Set token explicitly, or use
GITHUB_PULL_REQUEST_TOKEN, to avoid sharing it.
Older versions: token is required when the feature is enabled, and must be a ${NAME} reference such as "${GITHUB_PULL_REQUEST_TOKEN}". There is no environment variable fallback.
3. Choose which repositories it may look up
The worker reports its own repository, so the server must decide which repositories the token may be used for. Without a scope the feature stays dormant. There are two ways:
allowedRepositories: a list ofowner/nameorowner/*entries (up to 256). Matching is case-insensitive. Entries such as*/*,*,owner, or ones containing..are rejected by the schema.allowAllRepositories: true(Pending): look up any repository the token can read. Default isfalse.
Security: allowAllRepositories
With this on, any user who can run code can point the server's token at any repository that token can read, and see the pull request title, size and check status. Use a read-only token scoped to the repositories you are willing to show.
4. Turn it on
Pending: not yet released
Default-on behavior is part of LibreChat-AI/LibreChat#16876.
The feature is on as soon as there is a token and a repository scope. There is no enabled switch to set. enabled: false turns it off, whatever else is set.
Older versions: enabled: true is required, and the schema rejects enabled: true without both token and a non-empty allowedRepositories.
Minimal examples
Default-on form (Pending), with GITHUB_TOKEN set in the server environment:
endpoints:
agents:
pullRequests:
allowAllRepositories: trueRestricted to specific repositories (Pending, same token fallback):
endpoints:
agents:
pullRequests:
allowedRepositories:
- 'LibreChat-AI/LibreChat'
- 'my-org/*'Form that works on older versions:
endpoints:
agents:
pullRequests:
enabled: true
token: '${GITHUB_PULL_REQUEST_TOKEN}'
allowedRepositories: ['LibreChat-AI/LibreChat']See the pullRequests reference for every key, including the tuning options.
What users see
Chat header chip
- Desktop: the chip sits on the left of the header, next to the other left controls. Hovering or focusing it opens the card to its right.
- Mobile: the entry is in the header's overflow (three dots) menu and opens a dialog.
- The header refreshes on its own: about every 20 seconds while checks are running or mergeability is still being computed, about every 60 seconds otherwise, and not at all once a pull request is merged or closed with no checks running.
Sidebar mark
- A pull request icon with a CI dot sits at the end of the conversation row. It appears only for conversations that have a pull request.
- Hover, focus or click opens the same card to the right of the mark. Clicking the mark does not open the conversation.
- The sidebar never polls. A row is as fresh as the last time the list loaded, and it refreshes when the window regains focus and its data is older than a minute.
- If a lookup fails, the row shows a warning icon with a retry button in its card, so a failure is not mistaken for "no pull request".
The card
The card shows PR #number, +additions in green, -deletions in red, a link that opens the pull request on GitHub, the pull request title, and two badges:
- State: Open, Draft, Merged or Closed.
- Checks: Passing, Failing, Running, or none.
Icon and dot meaning
| Situation | Icon color | CI dot |
|---|---|---|
| Open, no conflicts, checks passing | Green (ready to merge) | Green |
| Open, no conflicts, no checks at all | Green | None |
| Open, merge conflicts | Red (cannot merge) | By checks |
| Draft | Neutral | By checks |
| Merged or closed | Neutral | None (amber while checks still run) |
CI dot colors: green for passing, red for failing, amber for running, and none when there are no checks, or when the pull request is merged or closed and no checks are still running. A failed check outranks one still running. A rollup with more check runs than were read is never reported as passing.
How a pull request is matched
- The worker reports the repository, branch and commit (head) the chat last ran at.
- LibreChat lists open pull requests for that branch first, so closed history on a reused branch name cannot hide an open one. Then it checks open pull requests from forks (found through the commit), then closed ones.
- With a recorded commit, a pull request counts only if its head is that commit or builds on it. This keeps a deleted and reused branch name from showing an old pull request.
- A branch with no commit yet (a new, empty branch) matches nothing.
- The result is cached per credential, repository, branch and commit.
- Check runs are read for the pull request's current head. For a pull request from a fork, checks are read from the fork only when that fork's repository is in scope (listed, or
allowAllRepositories). Otherwise the base repository is read and the token never goes to an unlisted fork.
Behavior
Rate limits
- A GitHub rate limit applies to the credential, so it pauses lookups for every branch under that token.
- The minimum pause is 10 seconds. A secondary limit with no wait given waits 60 seconds. A wait that GitHub names is honored, capped at 1 hour.
- During a pause, lookups answer
RATE_LIMITEDand rows show the retry state.
Rolling upgrades
- The sidebar asks the batch route only when the startup config says the server has it (
pullRequestsBatchVersion). - If a replica without the route answers 404, the client falls back to the single route, one call at a time, or at
pullRequestsMaxConcurrentLookupswhen the server advertised it. This covers a client that received the new startup flag from an upgraded replica and then reached a replica without the route. If/api/configis served by a replica without the flag, the sidebar never asks the batch route and rows stay empty, so upgrade every replica before relying on sidebar marks.
Defaults when unset
- With no token or no repository scope, the feature is dormant: nothing is advertised in the startup config, nothing is recorded, nothing is shown.
- With the feature off in the config, existing chats are unchanged and the client never calls the new routes.
Security model
- The token lives on the server. It is never sent to the browser, and error responses never include GitHub's response text, the token, or stack traces.
- The token is used only for repositories in scope. It is never sent to a fork that is not in scope.
- The two
/api/convoslookup routes require an authenticated user and are owner-scoped. A conversation that is missing, expired, or belongs to another user looks the same as one with no pull request. - Use a read-only, fine-grained token limited to the repositories you are willing to show.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No chip and no sidebar mark anywhere | No token, no repository scope, or enabled: false | Check Setup. Open /api/config while signed in and look for pullRequestsEnabled: true. |
| Header chip works, sidebar rows show nothing | pullRequestsBatchVersion is missing from /api/config (old build, or a stale tab) | Rebuild or upgrade the server, restart, and hard reload. Confirm pullRequestsBatchVersion: 1. |
| Chip appears for some chats only | Only chats on an attached code workspace that has reported a branch have a lane | Confirm CODEAPI_BRIDGE_LANE_GIT=true on the Code API and a worker built from code-interpreter PR #311 or later. |
| Warning icon with retry | Lookup failed (rate limit, GitHub error, or NOT_CONFIGURED) | Retry. If it is constant, check the token and its permissions. |
NOT_CONFIGURED | token is set but its variable is empty or missing | Set the variable. Pending: or remove token to use the fallback variables. |
| Pull requests load but checks look wrong or fail | The token lacks Checks or Contents permission | Grant read-only Checks and Contents. |
| Pull request from a fork shows no checks | The fork is not in scope, so checks are read from the base repository | Add the fork owner to allowedRepositories. Pending: or set allowAllRepositories: true. |
| No pull request for a branch name that has been reused many times | maxCandidatePullRequests, maxCandidatePages or maxHeadComparisons is too low for how often the name is reused, so the search stops before it reaches the matching pull request | Raise them. |
| Slow or timing-out lookups behind a proxy | requestTimeoutSeconds is too low | Raise it. Check HTTPS_PROXY and NO_PROXY. |
API reference
Both /api/convos routes below require an authenticated user and are owner-scoped. The startup config endpoint is not owner-scoped.
Get one conversation's pull request
GET /api/convos/:conversationId/pull-request
- 200:
{ "pullRequest": null }or{ "pullRequest": { number, title, url, additions, deletions, state, isDraft, mergeable, checks } }. stateisopen,closedormerged.mergeableisclean,conflictingorunknown.checksispassing,failing,runningornone.- 503:
{ "error": "...", "code": "NOT_CONFIGURED" | "RATE_LIMITED" | "UPSTREAM_ERROR" }. - 404: invalid conversation id. 500: storage error with fixed text.
Get many conversations' pull requests
POST /api/convos/pull-requests
- Body:
{ "conversationIds": ["..."] }, with 1 to 50 ids. Duplicates collapse. Anything else is a 400. - 200:
{ "results": [ { "conversationId", "pullRequest" } | { "conversationId", "error": { "code" } } ] }, in the order asked. One failing lookup does not hide the others. - A missing token fails only the entries that needed it.
Startup config
GET /api/config includes these fields. The server sets them; they are not settings.
pullRequestsEnabled(boolean)pullRequestsBatchVersion(1, present only with the feature on)pullRequestsMaxConcurrentLookups(number, present only with the feature on)
Only the error codes listed above are stable.
How is this guide?