mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-03 22:32:42 +00:00
🔗 chore: Dispatch GitNexus Deploy When Index Is Bot-Triggered (#12621)
* fix: dispatch deploy from index when triggered by github-actions[bot] GitHub Actions suppresses workflow_run events for workflow runs whose triggering actor is GITHUB_TOKEN (to prevent recursive chains). This means when gitnexus-pr-command.yml uses `gh api workflow_dispatch` to kick off gitnexus-index.yml, the downstream gitnexus-deploy-do.yml workflow_run trigger never fires — the PR command indexes the PR but the new artifact never makes it onto the droplet. Add a final step in gitnexus-index.yml that dispatches the deploy workflow directly via API, but ONLY when the triggering actor is github-actions[bot]. User-triggered runs (push, pull_request, manual workflow_dispatch from the UI) continue to rely on workflow_run as before, so we don't double-deploy. Requires a new actions:write permission at the workflow level for this dispatch. contents:read is unchanged. * fix: resolve main/dev indexes by artifact name, not branch run query The resolve step was querying listWorkflowRuns filtered by branch=main and branch=dev, then assuming the latest successful run on each branch produced the expected gitnexus-index-main / gitnexus-index-dev artifact. That assumption breaks for /gitnexus index command runs: The PR command workflow dispatches gitnexus-index.yml with ref=main (because that's where the workflow file lives) and an input pr_number. The resulting run has head_branch='main' but uploads its artifact as gitnexus-index-pr-<N>, not gitnexus-index-main. listWorkflowRuns returns that run as the "latest success on main", the download step tries to fetch gitnexus-index-main from it, and the API returns "no artifact matches any of the names or patterns provided". Fix: resolve all indexes (main, dev, and PRs) through the same listArtifactsForRepo path the PR discovery already uses. Looks up the freshest non-expired artifact by name directly, so the run's head_branch and event type don't matter — if the artifact exists, we find it; if not, we warn and move on. Side benefit: the resolution logic is now shorter and consistent across branches and PRs. * fix: paginate open PRs and parallelize artifact lookups The resolve step was capped at 100 open PRs by github.rest.pulls.list's per_page ceiling — LibreChat has 200+ open at any given time, so the tail of the PR queue was silently skipped. On top of that, the inner artifact lookup loop was serial, so even after pagination the resolve step would take 40-60 seconds on a busy repo (one API call per PR). - Replace the single-page rest.pulls.list call with github.paginate, which follows the Link header across pages and returns the full open-PR set regardless of count. - Drop the 100-PR truncation warning that was a known-limitation notice for exactly this case. - Batch the per-PR artifact lookups into groups of 10 via Promise.all. 200 PRs now take ~10 seconds instead of ~60, and the burst stays well within the authenticated rate limit (5000/hr). - Add a final core.info summary showing how many of the open PRs actually had a servable index artifact, so the log is useful for debugging why a specific PR isn't showing up on the droplet.
This commit is contained in:
parent
990763cbee
commit
8cb5c62fa1
2 changed files with 81 additions and 40 deletions
100
.github/workflows/gitnexus-deploy-do.yml
vendored
100
.github/workflows/gitnexus-deploy-do.yml
vendored
|
|
@ -153,10 +153,18 @@ jobs:
|
|||
sparse-checkout: .do/gitnexus
|
||||
fetch-depth: 1
|
||||
|
||||
# Resolve every index to serve. For main/dev this is simple: latest
|
||||
# successful run per branch. For PRs, we list artifacts across recent
|
||||
# workflow runs, match gitnexus-index-pr-<N>, then cross-reference
|
||||
# GitHub PR state and only keep artifacts whose PR is still open.
|
||||
# Resolve every index to serve. All resolutions go through
|
||||
# listArtifactsForRepo keyed by the expected artifact name, so a
|
||||
# run's branch or event type doesn't matter — we always pick the
|
||||
# freshest artifact that actually exists.
|
||||
#
|
||||
# Why this matters: a /gitnexus index command dispatches
|
||||
# gitnexus-index.yml with ref=main and an input pr_number, which
|
||||
# produces a run whose head_branch is "main" but whose artifact
|
||||
# is gitnexus-index-pr-<N>. listWorkflowRuns(branch='main') would
|
||||
# happily return that run, and we'd then try to download a
|
||||
# nonexistent gitnexus-index-main artifact from it. Querying by
|
||||
# artifact name directly avoids the whole mess.
|
||||
- name: Resolve indexes to serve
|
||||
id: resolve
|
||||
uses: actions/github-script@v7
|
||||
|
|
@ -164,58 +172,69 @@ jobs:
|
|||
script: |
|
||||
const serve = []; // [{ name, artifactName, runId }]
|
||||
|
||||
// --- main and dev branches ---
|
||||
for (const branch of ['main', 'dev']) {
|
||||
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||
// Helper — pick the newest non-expired artifact matching a name.
|
||||
const latestArtifact = async (artifactName) => {
|
||||
const { data } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'gitnexus-index.yml',
|
||||
branch,
|
||||
status: 'success',
|
||||
per_page: 1,
|
||||
name: artifactName,
|
||||
per_page: 10,
|
||||
});
|
||||
if (data.workflow_runs.length) {
|
||||
const runId = data.workflow_runs[0].id;
|
||||
const name = branch === 'main' ? 'LibreChat' : `LibreChat-${branch}`;
|
||||
serve.push({ name, artifactName: `gitnexus-index-${branch}`, runId });
|
||||
core.info(`${branch}: run ${runId} -> ${name}`);
|
||||
} else {
|
||||
core.warning(`No successful index run found for ${branch}`);
|
||||
return data.artifacts
|
||||
.filter((a) => !a.expired)
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
|
||||
};
|
||||
|
||||
// --- main and dev branches ---
|
||||
for (const [branch, name] of [
|
||||
['main', 'LibreChat'],
|
||||
['dev', 'LibreChat-dev'],
|
||||
]) {
|
||||
const artifactName = `gitnexus-index-${branch}`;
|
||||
const fresh = await latestArtifact(artifactName);
|
||||
if (!fresh) {
|
||||
core.warning(`No artifact found for ${branch} (expected ${artifactName})`);
|
||||
continue;
|
||||
}
|
||||
serve.push({
|
||||
name,
|
||||
artifactName,
|
||||
runId: fresh.workflow_run.id,
|
||||
});
|
||||
core.info(`${branch}: run ${fresh.workflow_run.id} -> ${name}`);
|
||||
}
|
||||
|
||||
// --- open PRs with at least one successful index run ---
|
||||
const { data: openPrs } = await github.rest.pulls.list({
|
||||
// github.paginate handles the 100-per-page ceiling automatically
|
||||
// so the resolution works on repos with 200+ concurrent open PRs.
|
||||
const openPrs = await github.paginate(github.rest.pulls.list, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
core.info(`Found ${openPrs.length} open PRs`);
|
||||
if (openPrs.length === 100) {
|
||||
core.warning(
|
||||
'Open PR list was truncated at 100 (GitHub API maximum). ' +
|
||||
'Some PR indexes may be skipped. Add pagination if the repo ' +
|
||||
'regularly exceeds 100 concurrent open PRs.',
|
||||
|
||||
// Parallelize artifact lookups in fixed-size batches so the
|
||||
// resolve step runs in seconds instead of minutes on big repos,
|
||||
// without burning the GitHub API rate limit all at once.
|
||||
const BATCH_SIZE = 10;
|
||||
const prMatches = [];
|
||||
for (let i = 0; i < openPrs.length; i += BATCH_SIZE) {
|
||||
const batch = openPrs.slice(i, i + BATCH_SIZE);
|
||||
const results = await Promise.all(
|
||||
batch.map(async (pr) => {
|
||||
const artifactName = `gitnexus-index-pr-${pr.number}`;
|
||||
const fresh = await latestArtifact(artifactName);
|
||||
return fresh ? { pr, artifactName, fresh } : null;
|
||||
}),
|
||||
);
|
||||
for (const hit of results) {
|
||||
if (hit) prMatches.push(hit);
|
||||
}
|
||||
}
|
||||
|
||||
for (const pr of openPrs) {
|
||||
// PR branches live on forks too. listWorkflowRuns for a fork
|
||||
// branch name doesn't return anything useful, so we instead
|
||||
// query artifacts directly filtered by name.
|
||||
const artifactName = `gitnexus-index-pr-${pr.number}`;
|
||||
const { data: arts } = await github.rest.actions.listArtifactsForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: artifactName,
|
||||
per_page: 5,
|
||||
});
|
||||
// Pick the most recent non-expired artifact
|
||||
const fresh = arts.artifacts
|
||||
.filter((a) => !a.expired)
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
|
||||
if (!fresh) continue;
|
||||
for (const { pr, artifactName, fresh } of prMatches) {
|
||||
serve.push({
|
||||
name: `LibreChat-pr-${pr.number}`,
|
||||
artifactName,
|
||||
|
|
@ -223,6 +242,7 @@ jobs:
|
|||
});
|
||||
core.info(`PR #${pr.number}: run ${fresh.workflow_run.id} -> LibreChat-pr-${pr.number}`);
|
||||
}
|
||||
core.info(`Resolved ${prMatches.length} PR indexes out of ${openPrs.length} open PRs`);
|
||||
|
||||
if (!serve.length) {
|
||||
core.setFailed('No indexes to serve');
|
||||
|
|
|
|||
21
.github/workflows/gitnexus-index.yml
vendored
21
.github/workflows/gitnexus-index.yml
vendored
|
|
@ -31,6 +31,7 @@ on:
|
|||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write # needed to dispatch gitnexus-deploy-do.yml on bot-triggered runs
|
||||
|
||||
concurrency:
|
||||
# When triggered by the /gitnexus command, group by PR number so rapid
|
||||
|
|
@ -154,3 +155,23 @@ jobs:
|
|||
path: .gitnexus/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
|
||||
# GitHub suppresses workflow_run events for workflow runs whose
|
||||
# triggering actor is GITHUB_TOKEN (to prevent recursive chaining).
|
||||
# That means when this workflow is dispatched by gitnexus-pr-command
|
||||
# via `gh api workflow_dispatch`, the deploy workflow's workflow_run
|
||||
# trigger never fires. Manually dispatch the deploy here in that
|
||||
# specific case — user-triggered runs continue to rely on the
|
||||
# existing workflow_run trigger, so we don't double-deploy.
|
||||
- name: Trigger deploy workflow for bot-triggered runs
|
||||
if: github.triggering_actor == 'github-actions[bot]'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.info('Triggering actor is github-actions[bot]; workflow_run would not fire. Dispatching gitnexus-deploy-do.yml manually.');
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'gitnexus-deploy-do.yml',
|
||||
ref: 'main',
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue