name: Release Smoke on: workflow_dispatch: inputs: paperclip_version: description: Published Paperclip dist-tag to test required: true default: canary type: choice options: - canary - nightly - beta - latest host_port: description: Host port for the Docker smoke container required: false default: "3232" type: string artifact_name: description: Artifact name for uploaded diagnostics required: false default: release-smoke type: string workflow_call: inputs: paperclip_version: required: true type: string host_port: required: false default: "3232" type: string artifact_name: required: false default: release-smoke type: string jobs: # The Docker smoke below can never exercise the background-service leg of # onboarding: containers have no service manager, so v2026.824.0 shipped a # service install that crash-looped on a missing shim while every # golden-path check stayed green. Run the same published artifact directly # on the runner VM's systemd and require the installed service to end up # serving. smoke_service: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup Node.js uses: actions/setup-node@v7 with: node-version: 24 - name: Start a user systemd session # The hosted runner has no login session for the runner user, so # `systemctl --user` cannot reach a user manager until lingering # starts one. Export the session address for the steps below. run: | sudo loginctl enable-linger "$(id -un)" uid="$(id -u)" for _ in $(seq 1 30); do [[ -S "/run/user/$uid/bus" ]] && break sleep 1 done [[ -S "/run/user/$uid/bus" ]] { echo "XDG_RUNTIME_DIR=/run/user/$uid" echo "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus" } >> "$GITHUB_ENV" - name: Onboard with the background service env: PAPERCLIPAI_VERSION: ${{ inputs.paperclip_version }} DATA_DIR: ${{ runner.temp }}/service-smoke-data SMOKE_CLEANUP: "false" run: ./scripts/service-onboard-smoke.sh - name: Capture service diagnostics if: always() run: | { systemctl --user --no-pager status paperclipai.service || true journalctl --user -u paperclipai.service --no-pager || true } > "$RUNNER_TEMP/paperclipai-service.log" 2>&1 - name: Upload service diagnostics if: always() uses: actions/upload-artifact@v7 with: name: ${{ inputs.artifact_name }}-service path: ${{ runner.temp }}/paperclipai-service.log retention-days: 14 smoke: runs-on: ubuntu-latest timeout-minutes: 45 steps: - name: Checkout repository uses: actions/checkout@v7 - name: Setup pnpm uses: pnpm/action-setup@v6 with: version: 9.15.4 - name: Setup Node.js uses: actions/setup-node@v7 with: node-version: 24 cache: pnpm - name: Install dependencies run: pnpm install --no-frozen-lockfile - name: Verify runner Chrome # Release smoke also runs headless on GitHub's Ubuntu image, so use the # runner's preinstalled Chrome instead of a Playwright browser download. run: google-chrome --version - name: Launch Docker smoke harness run: | metadata_file="$RUNNER_TEMP/release-smoke.env" HOST_PORT="${{ inputs.host_port }}" \ DATA_DIR="$RUNNER_TEMP/release-smoke-data" \ PAPERCLIPAI_VERSION="${{ inputs.paperclip_version }}" \ SMOKE_READY_TIMEOUT_SECONDS=420 \ SMOKE_DETACH=true \ SMOKE_METADATA_FILE="$metadata_file" \ ./scripts/docker-onboard-smoke.sh set -a source "$metadata_file" set +a { echo "SMOKE_BASE_URL=$SMOKE_BASE_URL" echo "SMOKE_ADMIN_EMAIL=$SMOKE_ADMIN_EMAIL" echo "SMOKE_ADMIN_PASSWORD=$SMOKE_ADMIN_PASSWORD" echo "SMOKE_CONTAINER_NAME=$SMOKE_CONTAINER_NAME" echo "SMOKE_DATA_DIR=$SMOKE_DATA_DIR" echo "SMOKE_IMAGE_NAME=$SMOKE_IMAGE_NAME" echo "SMOKE_PAPERCLIPAI_VERSION=$SMOKE_PAPERCLIPAI_VERSION" echo "SMOKE_METADATA_FILE=$metadata_file" } >> "$GITHUB_ENV" - name: Run release smoke Playwright suite env: PAPERCLIP_RELEASE_SMOKE_BASE_URL: ${{ env.SMOKE_BASE_URL }} PAPERCLIP_RELEASE_SMOKE_EMAIL: ${{ env.SMOKE_ADMIN_EMAIL }} PAPERCLIP_RELEASE_SMOKE_PASSWORD: ${{ env.SMOKE_ADMIN_PASSWORD }} PAPERCLIP_PLAYWRIGHT_CHANNEL: "chrome" run: pnpm run test:release-smoke - name: Capture Docker logs if: always() run: | if [[ -n "${SMOKE_CONTAINER_NAME:-}" ]]; then docker logs "$SMOKE_CONTAINER_NAME" >"$RUNNER_TEMP/docker-onboard-smoke.log" 2>&1 || true fi - name: Upload diagnostics if: always() uses: actions/upload-artifact@v7 with: name: ${{ inputs.artifact_name }} path: | ${{ runner.temp }}/docker-onboard-smoke.log ${{ env.SMOKE_METADATA_FILE }} tests/release-smoke/playwright-report/ tests/release-smoke/test-results/ retention-days: 14 - name: Stop Docker smoke container if: always() run: | if [[ -n "${SMOKE_CONTAINER_NAME:-}" ]]; then docker rm -f "$SMOKE_CONTAINER_NAME" >/dev/null 2>&1 || true fi