Skip to content

CI (OpenClaw E2E)

CI (OpenClaw E2E) #11

Workflow file for this run

# OpenClaw end-to-end CI.
#
# Triggers, in order of intent:
# 1. PRs that touch OpenClaw-relevant paths (auto, primary signal)
# 2. Pushes to main that touch the same paths (post-merge regression catch)
# 3. Daily schedule at 06:00 UTC (catches environment drift —
# OpenClaw version bumps, MCP SDK
# breaks, npm install regressions)
# 4. Adding the `ci:openclaw` label to a PR (manual re-run on matching PRs)
# 5. Workflow_dispatch from the Actions UI (manual force-run on any ref,
# no PR needed)
#
# The path filter at the trigger level is the primary gate, so non-OpenClaw PRs
# pay no CI cost. The label is only useful as a re-trigger on PRs that already
# matched paths; for force-running against an unrelated branch, use the manual
# dispatch button in the Actions UI.
name: CI (OpenClaw E2E)
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, labeled, unlabeled]
paths:
- "app/integrations/openclaw.py"
- "app/tools/OpenClawMCPTool/**"
- "tests/e2e/openclaw/**"
- "tests/utils/alert_factory/**"
- ".github/workflows/e2e-openclaw.yml"
- "docs/openclaw.mdx"
- ".nvmrc"
push:
branches: [main]
paths:
- "app/integrations/openclaw.py"
- "app/tools/OpenClawMCPTool/**"
- "tests/e2e/openclaw/**"
- "tests/utils/alert_factory/**"
- ".github/workflows/e2e-openclaw.yml"
- "docs/openclaw.mdx"
- ".nvmrc"
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
concurrency:
group: ci-openclaw-${{ github.ref }}
cancel-in-progress: true
jobs:
# Quality gates (lint / format-check / mypy) are run by the main
# ``ci.yml`` workflow for every PR. We don't duplicate them here —
# this workflow's sole job is the OpenClaw e2e suite. If you reach
# this workflow, your PR has already cleared the quality gates.
openclaw-test:
name: openclaw test
runs-on: ubuntu-latest
timeout-minutes: 25
# No ``env:`` block by design. CI runs the boot + use-case
# scenarios only — the full-RCA sub-tests call an LLM, cost API
# credits, and take ~25s each, so we deliberately don't wire any
# LLM secret in. They auto-skip via
# ``LLM_CREDENTIAL_SKIP_REASON`` when no key is present in env.
# Contributors with ANTHROPIC_API_KEY / OPENAI_API_KEY /
# GEMINI_API_KEY set locally still run them via
# ``make test-openclaw``. Lang Smith env vars stay scoped to the
# local-RCA path for the same reason — no value tracing the
# use-case-only CI runs.
steps:
- uses: actions/checkout@v5
- name: Set up Node (OpenClaw requires >=22.12; version pinned in .nvmrc)
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
- name: Install OpenClaw CLI
# ``continue-on-error: true`` so an npm-registry hiccup, a package-name
# rename on OpenClaw's side, or a network blip does not kill the whole
# workflow. The "Probe OpenClaw availability" step below catches the
# failure and downgrades the run to "tests skipped" with a clear
# explanation rather than crashing.
continue-on-error: true
id: install_openclaw
run: npm install -g openclaw
- name: Probe OpenClaw availability
# Whether or not ``npm install`` succeeded, check if ``openclaw`` is
# actually runnable. Failure modes we surface explicitly:
# - CLI not installed (npm step failed silently)
# - CLI installed but Node version mismatch (older runner image)
# - CLI installed but the bundled JS errored on first run
# ``openclaw_runnable`` env var feeds the next step's gating.
run: |
set +e
if ! command -v openclaw >/dev/null 2>&1; then
echo "::warning::openclaw CLI is not on PATH (npm install may have failed); the e2e suite will be skipped this run."
echo "openclaw_runnable=false" >> "$GITHUB_ENV"
exit 0
fi
version_output="$(openclaw --version 2>&1)"
if [ $? -ne 0 ]; then
echo "::warning::openclaw is on PATH but errors when run:"
echo "$version_output"
echo "::warning::Skipping e2e suite this run."
echo "openclaw_runnable=false" >> "$GITHUB_ENV"
exit 0
fi
echo "✓ OpenClaw verified: $version_output"
echo "openclaw_runnable=true" >> "$GITHUB_ENV"
- name: Note LLM credential policy
# CI deliberately does not wire any LLM key, so the full-RCA
# sub-tests skip every run. This step is informational — it
# makes the policy obvious in the run log instead of leaving a
# silent skip that a reviewer has to dig for. Run RCA locally
# with ``make test-openclaw`` if you have a key configured.
run: |
echo "ℹ︎ CI policy: full-RCA sub-tests are skipped (LLM credentials intentionally not wired)."
echo " Boot + use-case sub-tests still run and gate the merge."
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install dependencies
run: uv sync --frozen --extra dev
- name: Run OpenClaw E2E suite
# Skips with a clear message if the OpenClaw probe failed earlier,
# so the workflow finishes green with a documented "couldn't run"
# status rather than a confusing red on the install step.
run: |
if [ "$openclaw_runnable" != "true" ]; then
echo "::warning::Skipping pytest invocation — openclaw CLI unavailable in this environment. See earlier warnings."
exit 0
fi
uv run python -m pytest -m e2e -v tests/e2e/openclaw/
- name: Upload gateway logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openclaw-gateway-logs
path: /tmp/openclaw-e2e-logs/
if-no-files-found: ignore
retention-days: 7
- name: Summary
# Always runs, regardless of earlier step outcomes. The probe
# step above downgrades a missing-CLI / wrong-Node environment
# to "tests skipped" rather than failing the workflow, so this
# summary is the one place a reviewer sees why fewer tests ran
# than expected. Real pytest failures still fail the job.
if: always()
run: |
echo "### OpenClaw E2E run summary"
echo " OpenClaw runnable: ${openclaw_runnable:-unknown}"
echo " Full-RCA sub-tests: skipped by CI policy (no LLM key wired)"
echo " Run RCA locally with: make test-openclaw"