Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: Stack smoke test

on:
schedule:
- cron: '0 13 * * *' # daily ~13:00 UTC
pull_request:
paths:
- '**/*compose*.yml'
- 'scripts/**'
- 'librechat.yaml'
- 'librechat.example.yaml'
- 'librechat.example.env'
- '.env.example'
- '.github/workflows/smoke-test.yml'
workflow_dispatch:

# Avoid piling up runs on the same ref.
concurrency:
group: smoke-test-${{ github.ref }}
cancel-in-progress: true

jobs:
smoke-test:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v5
Comment thread
graphaelli marked this conversation as resolved.

- name: Generate .env
run: bash scripts/generate-env.sh

# --wait blocks until every healthchecked service is healthy and the rest
# are running; it fails if any service is unhealthy or a one-shot exits
# non-zero. This is the core "comes up cleanly" assertion.
- name: Launch stack and wait for health
run: docker compose up -d --wait --wait-timeout 600

- name: Probe service endpoints
run: |
set -euo pipefail

# probe <name> <url> <mode>
# mode=ok -> require an HTTP 2xx/3xx status
# mode=any -> any HTTP response is success (endpoint is auth-gated)
probe() {
local name="$1" url="$2" mode="$3"
local code
for i in $(seq 1 30); do
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url" || true)"
code="${code:-000}"
if [ "$mode" = "any" ] && [ "$code" != "000" ]; then
echo "✅ $name -> HTTP $code ($url)"; return 0
fi
if [ "$mode" = "ok" ] && [ "$code" -ge 200 ] && [ "$code" -lt 400 ]; then
echo "✅ $name -> HTTP $code ($url)"; return 0
fi
sleep 5
done
echo "❌ $name did not respond as expected (last HTTP $code, $url)"; return 1
}

probe "LibreChat" "http://localhost:3080/" ok
probe "Langfuse" "http://localhost:3000/" ok
probe "Admin Panel" "http://localhost:3081/" ok
# MCP requires CLICKHOUSE_MCP_AUTH_TOKEN; any HTTP response proves it's
# listening. Depth is already covered by its compose healthcheck.
probe "ClickHouse MCP" "http://localhost:8000/" any

- name: Dump diagnostics on failure
if: failure()
run: |
docker compose ps -a
docker compose logs --no-color --tail=200

- name: Tear down
if: always()
run: docker compose down -v

# On a failed DAILY run, open (or comment on) a tracking issue. PR failures
# surface as a red check on the PR, so they don't need an issue.
report-daily-failure:
needs: smoke-test
if: contains(fromJSON('["failure", "cancelled"]'), needs.smoke-test.result) && github.event_name == 'schedule'
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Open or update tracking issue
uses: actions/github-script@v7
with:
script: |
const label = 'smoke-test-failure';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = `The daily stack smoke test failed.\n\nRun: ${runUrl}`;

// Ensure the tracking label exists so the search below (and the
// de-dupe it powers) is reliable in a repo that has never had it.
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
});
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: 'd73a4a',
});
}

const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: label,
});

// listForRepo returns PRs as well as issues; exclude PRs so we
// never comment on a mislabeled pull request.
const openIssue = existing.data.find(i => !i.pull_request);

if (openIssue) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: openIssue.number,
body,
});
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Daily stack smoke test failed',
body,
labels: [label],
Comment thread
graphaelli marked this conversation as resolved.
});
}