Skip to content

Commit a58b819

Browse files
graphaelliclaudedustinhealy
authored
ci: add stack smoke test (daily + on PRs) (#11)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com>
1 parent c887067 commit a58b819

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

‎.github/workflows/smoke-test.yml‎

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
name: Stack smoke test
2+
3+
on:
4+
schedule:
5+
- cron: '0 13 * * *' # daily ~13:00 UTC
6+
pull_request:
7+
paths:
8+
- '**/*compose*.yml'
9+
- 'scripts/**'
10+
- 'librechat.yaml'
11+
- 'librechat.example.yaml'
12+
- 'librechat.example.env'
13+
- '.env.example'
14+
- '.github/workflows/smoke-test.yml'
15+
workflow_dispatch:
16+
17+
# Avoid piling up runs on the same ref.
18+
concurrency:
19+
group: smoke-test-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
smoke-test:
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 25
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v5
29+
30+
- name: Generate .env
31+
run: bash scripts/generate-env.sh
32+
33+
# --wait blocks until every healthchecked service is healthy and the rest
34+
# are running; it fails if any service is unhealthy or a one-shot exits
35+
# non-zero. This is the core "comes up cleanly" assertion.
36+
- name: Launch stack and wait for health
37+
run: docker compose up -d --wait --wait-timeout 600
38+
39+
- name: Probe service endpoints
40+
run: |
41+
set -euo pipefail
42+
43+
# probe <name> <url> <mode>
44+
# mode=ok -> require an HTTP 2xx/3xx status
45+
# mode=any -> any HTTP response is success (endpoint is auth-gated)
46+
probe() {
47+
local name="$1" url="$2" mode="$3"
48+
local code
49+
for i in $(seq 1 30); do
50+
code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url" || true)"
51+
code="${code:-000}"
52+
if [ "$mode" = "any" ] && [ "$code" != "000" ]; then
53+
echo "✅ $name -> HTTP $code ($url)"; return 0
54+
fi
55+
if [ "$mode" = "ok" ] && [ "$code" -ge 200 ] && [ "$code" -lt 400 ]; then
56+
echo "✅ $name -> HTTP $code ($url)"; return 0
57+
fi
58+
sleep 5
59+
done
60+
echo "❌ $name did not respond as expected (last HTTP $code, $url)"; return 1
61+
}
62+
63+
probe "LibreChat" "http://localhost:3080/" ok
64+
probe "Langfuse" "http://localhost:3000/" ok
65+
probe "Admin Panel" "http://localhost:3081/" ok
66+
# MCP requires CLICKHOUSE_MCP_AUTH_TOKEN; any HTTP response proves it's
67+
# listening. Depth is already covered by its compose healthcheck.
68+
probe "ClickHouse MCP" "http://localhost:8000/" any
69+
70+
- name: Dump diagnostics on failure
71+
if: failure()
72+
run: |
73+
docker compose ps -a
74+
docker compose logs --no-color --tail=200
75+
76+
- name: Tear down
77+
if: always()
78+
run: docker compose down -v
79+
80+
# On a failed DAILY run, open (or comment on) a tracking issue. PR failures
81+
# surface as a red check on the PR, so they don't need an issue.
82+
report-daily-failure:
83+
needs: smoke-test
84+
if: contains(fromJSON('["failure", "cancelled"]'), needs.smoke-test.result) && github.event_name == 'schedule'
85+
runs-on: ubuntu-latest
86+
permissions:
87+
issues: write
88+
steps:
89+
- name: Open or update tracking issue
90+
uses: actions/github-script@v7
91+
with:
92+
script: |
93+
const label = 'smoke-test-failure';
94+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
95+
const body = `The daily stack smoke test failed.\n\nRun: ${runUrl}`;
96+
97+
// Ensure the tracking label exists so the search below (and the
98+
// de-dupe it powers) is reliable in a repo that has never had it.
99+
try {
100+
await github.rest.issues.getLabel({
101+
owner: context.repo.owner,
102+
repo: context.repo.repo,
103+
name: label,
104+
});
105+
} catch (e) {
106+
if (e.status !== 404) throw e;
107+
await github.rest.issues.createLabel({
108+
owner: context.repo.owner,
109+
repo: context.repo.repo,
110+
name: label,
111+
color: 'd73a4a',
112+
});
113+
}
114+
115+
const existing = await github.rest.issues.listForRepo({
116+
owner: context.repo.owner,
117+
repo: context.repo.repo,
118+
state: 'open',
119+
labels: label,
120+
});
121+
122+
// listForRepo returns PRs as well as issues; exclude PRs so we
123+
// never comment on a mislabeled pull request.
124+
const openIssue = existing.data.find(i => !i.pull_request);
125+
126+
if (openIssue) {
127+
await github.rest.issues.createComment({
128+
owner: context.repo.owner,
129+
repo: context.repo.repo,
130+
issue_number: openIssue.number,
131+
body,
132+
});
133+
} else {
134+
await github.rest.issues.create({
135+
owner: context.repo.owner,
136+
repo: context.repo.repo,
137+
title: 'Daily stack smoke test failed',
138+
body,
139+
labels: [label],
140+
});
141+
}

0 commit comments

Comments
 (0)