feat(cli): add telegram channel adapter, scoped config, and diagnostics (replacement) #924
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI — Tests | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - dev | |
| - preview | |
| - main | |
| workflow_call: | |
| inputs: | |
| run_ui_e2e: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_mobile_e2e_android: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_mobile_e2e_ios: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_ui: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_server: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_server_db_contract: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_cli: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_stack: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_typecheck: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_cli_daemon_e2e: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_e2e_core: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_e2e_core_slow: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_providers: | |
| required: false | |
| default: false | |
| type: boolean | |
| providers_preset: | |
| required: false | |
| default: all | |
| type: string | |
| providers_tier: | |
| required: false | |
| default: smoke | |
| type: string | |
| providers_opencode: | |
| required: false | |
| default: "1.1.52|c7c1e622aff3d457358385e28dff33ffa8401cb9067e865a9f35a2f94e899490" | |
| type: string | |
| providers_flags: | |
| required: false | |
| default: '{"strict_keys":false,"flake_retry":true,"update_baselines":false}' | |
| type: string | |
| providers_models: | |
| required: false | |
| default: '{"default":"","overrides":""}' | |
| type: string | |
| run_stress: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_release_contracts: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_installers_smoke: | |
| required: false | |
| default: true | |
| type: boolean | |
| installers_channel: | |
| required: false | |
| default: stable | |
| type: string | |
| run_binary_smoke: | |
| required: false | |
| default: true | |
| type: boolean | |
| run_self_host_systemd: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_self_host_launchd: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_self_host_schtasks: | |
| required: false | |
| default: false | |
| type: boolean | |
| run_self_host_daemon: | |
| required: false | |
| default: false | |
| type: boolean | |
| stress_config: | |
| required: false | |
| default: '{"repeat":"5","seed":""}' | |
| type: string | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # Speed up CI: UI postinstall vendors large web-only assets (Monaco/Kokoro/Skia web). | |
| # Tests and native builds do not require these assets. | |
| HAPPIER_UI_VENDOR_WEB_ASSETS: "0" | |
| jobs: | |
| ui-e2e: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || (github.event_name == 'workflow_call' && inputs.run_ui_e2e) }} | |
| name: UI E2E (Playwright) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 35 | |
| env: | |
| # UI E2E boots Expo web; keep web assets available to avoid CI-only runtime surprises. | |
| HAPPIER_UI_VENDOR_WEB_ASSETS: "1" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Detect UI E2E-relevant changes | |
| id: changes | |
| if: github.event_name != 'workflow_call' | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| ui_e2e: | |
| - 'apps/ui/**' | |
| - 'apps/server/**' | |
| - 'apps/cli/**' | |
| - 'packages/tests/**' | |
| - 'package.json' | |
| - 'yarn.lock' | |
| - name: Skip UI E2E (no relevant changes) | |
| if: github.event_name != 'workflow_call' && steps.changes.outputs.ui_e2e != 'true' | |
| run: | | |
| echo "UI E2E skipped: no relevant changes." | |
| - name: Setup Node (GitHub Actions) | |
| if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true' | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true' | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile | |
| - name: Install Playwright browsers | |
| if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true' | |
| run: npx playwright install --with-deps chromium | |
| - name: Install sqlite3 (if missing) | |
| if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true' | |
| run: | | |
| if command -v sqlite3 >/dev/null 2>&1; then | |
| sqlite3 --version | |
| exit 0 | |
| fi | |
| sudo apt-get update | |
| sudo apt-get install -y sqlite3 | |
| sqlite3 --version | |
| - name: Run UI E2E | |
| if: github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true' | |
| run: yarn -s test:e2e:ui | |
| - name: Upload UI E2E artifacts (Playwright) | |
| if: (github.event_name == 'workflow_call' || steps.changes.outputs.ui_e2e == 'true') && failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ui-e2e-playwright-artifacts | |
| path: | | |
| packages/tests/.project/logs/e2e/ui-playwright | |
| .project/logs/e2e/*ui-e2e* | |
| if-no-files-found: ignore | |
| mobile-e2e-android: | |
| # Native E2E is intentionally opt-in for now (not a PR check). | |
| if: ${{ github.event_name == 'workflow_call' && inputs.run_mobile_e2e_android }} | |
| name: Mobile E2E (Maestro) (android) (opt-in) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| # Keep this lane mobile-focused (avoid downloading heavy web-only assets). | |
| HAPPIER_INSTALL_SCOPE: "ui,protocol,agents,tests" | |
| HAPPIER_UI_VENDOR_WEB_ASSETS: "0" | |
| run: yarn install --frozen-lockfile | |
| - name: Install Maestro | |
| run: | | |
| bash scripts/ci/install_maestro.sh | |
| - name: Run Mobile E2E (android) | |
| run: yarn -s test:e2e:mobile | |
| - name: Upload Mobile E2E artifacts (Maestro) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mobile-e2e-maestro-android-artifacts | |
| path: | | |
| packages/tests/.project/logs/e2e/mobile-maestro | |
| if-no-files-found: ignore | |
| mobile-e2e-ios: | |
| # Native E2E is intentionally opt-in for now (not a PR check). | |
| if: ${{ github.event_name == 'workflow_call' && inputs.run_mobile_e2e_ios }} | |
| name: Mobile E2E (Maestro) (ios) (opt-in) | |
| runs-on: macos-latest | |
| timeout-minutes: 45 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| HAPPIER_INSTALL_SCOPE: "ui,protocol,agents,tests" | |
| HAPPIER_UI_VENDOR_WEB_ASSETS: "0" | |
| run: yarn install --frozen-lockfile | |
| - name: Install Maestro | |
| run: | | |
| bash scripts/ci/install_maestro.sh | |
| - name: Run Mobile E2E (ios) | |
| run: yarn -s test:e2e:mobile:ios | |
| - name: Upload Mobile E2E artifacts (Maestro) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mobile-e2e-maestro-ios-artifacts | |
| path: | | |
| packages/tests/.project/logs/e2e/mobile-maestro | |
| if-no-files-found: ignore | |
| ui: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_ui }} | |
| name: UI Tests (unit + integration) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install Sapling | |
| run: | | |
| bash scripts/ci/install_sapling_ubuntu22.sh | |
| - name: Verify Sapling CLI | |
| run: sl --version | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile | |
| - name: Run protocol unit tests | |
| run: yarn workspace @happier-dev/protocol test | |
| - name: Run unit tests | |
| run: yarn workspace @happier-dev/app test:unit | |
| - name: Run integration tests | |
| run: yarn workspace @happier-dev/app test:integration | |
| shared-packages-unit: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_ui }} | |
| name: Shared Package Unit Tests | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run shared package unit tests | |
| run: | | |
| yarn workspace @happier-dev/transfers test | |
| yarn workspace @happier-dev/agents test | |
| yarn workspace @happier-dev/cli-common test | |
| yarn workspace @happier-dev/connection-supervisor test | |
| yarn --cwd packages/relay-server test | |
| server: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_server }} | |
| name: Server Tests (unit + integration) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run unit tests | |
| run: yarn --cwd apps/server test:unit | |
| - name: Run integration tests | |
| run: yarn --cwd apps/server test:integration | |
| server-db-contract: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_server_db_contract }} | |
| name: Server DB Contract (Postgres) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 25 | |
| services: | |
| postgres: | |
| image: postgres:17 | |
| env: | |
| POSTGRES_DB: happier | |
| POSTGRES_USER: happier | |
| POSTGRES_PASSWORD: happier | |
| ports: | |
| - 5432/tcp | |
| options: >- | |
| --health-cmd="pg_isready -U happier -d happier" | |
| --health-interval=10s | |
| --health-timeout=5s | |
| --health-retries=10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Migrate (Postgres) | |
| env: | |
| DATABASE_URL: postgresql://happier:happier@127.0.0.1:${{ job.services.postgres.ports[5432] }}/happier?sslmode=disable | |
| run: yarn --cwd apps/server -s prisma migrate deploy | |
| - name: Run db contract suite (Postgres) | |
| env: | |
| HAPPIER_DB_PROVIDER: postgres | |
| DATABASE_URL: postgresql://happier:happier@127.0.0.1:${{ job.services.postgres.ports[5432] }}/happier?sslmode=disable | |
| run: yarn --cwd apps/server test:server:db-contract | |
| cli: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_cli }} | |
| name: CLI Tests (unit + integration) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 25 | |
| env: | |
| HAPPIER_CLI_TMUX_INTEGRATION: "1" | |
| HAPPIER_CLI_DAEMON_REATTACH_INTEGRATION: "1" | |
| HAPPIER_NO_BROWSER_OPEN: "1" | |
| TMPDIR: /tmp | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install system dependencies | |
| run: | | |
| export DEBIAN_FRONTEND=noninteractive | |
| APT_FLAGS="-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30" | |
| if [ "${ACT:-}" = "true" ] || [ -d /var/run/act ]; then | |
| . /etc/os-release || true | |
| CODENAME="${VERSION_CODENAME:-noble}" | |
| ARCH="$(dpkg --print-architecture)" | |
| BASE_URL="http://ports.ubuntu.com/ubuntu-ports" | |
| if [ "${ARCH}" = "amd64" ]; then BASE_URL="http://archive.ubuntu.com/ubuntu"; fi | |
| if command -v sudo >/dev/null 2>&1; then | |
| sudo rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true | |
| echo "deb ${BASE_URL} ${CODENAME} main universe" | sudo tee /etc/apt/sources.list >/dev/null | |
| else | |
| rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true | |
| echo "deb ${BASE_URL} ${CODENAME} main universe" > /etc/apt/sources.list | |
| fi | |
| fi | |
| if command -v sudo >/dev/null 2>&1; then | |
| sudo apt-get ${APT_FLAGS} update | |
| sudo apt-get ${APT_FLAGS} install -y --no-install-recommends tmux | |
| else | |
| apt-get ${APT_FLAGS} update | |
| apt-get ${APT_FLAGS} install -y --no-install-recommends tmux | |
| fi | |
| tmux -V | |
| - name: Install Sapling | |
| run: | | |
| bash scripts/ci/install_sapling_ubuntu22.sh | |
| - name: Verify Sapling CLI | |
| run: sl --version | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Disable remote logging for CI (avoid hanging test process) | |
| run: | | |
| set -euo pipefail | |
| if [ -f apps/cli/.env.integration-test ]; then | |
| sed -i 's/^DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=.*/DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=/' apps/cli/.env.integration-test | |
| sed -i 's/^DEBUG=.*/DEBUG=/' apps/cli/.env.integration-test | |
| fi | |
| - name: Run unit tests | |
| run: yarn workspace @happier-dev/cli test:unit | |
| - name: Run integration tests | |
| run: yarn workspace @happier-dev/cli test:integration | |
| stack: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_stack }} | |
| name: Stack Tests (unit + integration) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run unit tests | |
| run: yarn --cwd apps/stack test:unit | |
| - name: Run integration tests | |
| run: yarn --cwd apps/stack test:integration | |
| release-contracts: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_release_contracts }} | |
| name: Release Contracts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run release contract tests | |
| run: | | |
| set -euo pipefail | |
| yarn -s test:release:contracts | |
| node scripts/pipeline/run.mjs release-sync-installers --check | |
| installers-smoke-linux: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }} | |
| name: Installer Smoke (Linux) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Verify published installers are synced | |
| run: | | |
| set -euo pipefail | |
| node scripts/pipeline/run.mjs release-sync-installers --check | |
| # Bootstrap-friendly: this repo can be green before the first GitHub release exists. | |
| # Once cli-stable is published, we enforce that the published installer can install it. | |
| - name: Detect CLI release tag | |
| id: cli_tag | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| tag="cli-stable" | |
| installer="install.sh" | |
| if [ "${INSTALLERS_CHANNEL}" = "preview" ]; then | |
| tag="cli-preview" | |
| installer="install-preview.sh" | |
| fi | |
| api="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" | |
| code="$(curl -sS -o /dev/null -w "%{http_code}" \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "${api}" || true)" | |
| if [ "${code}" = "200" ]; then | |
| echo "tag_exists=true" >> "$GITHUB_OUTPUT" | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "installer=${installer}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "tag_exists=false" >> "$GITHUB_OUTPUT" | |
| echo "Skipping installer smoke: ${tag} release tag not found (HTTP ${code})." | |
| - name: Run CLI installer from isolated temp location | |
| if: steps.cli_tag.outputs.tag_exists == 'true' | |
| env: | |
| HAPPIER_GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| export HOME="$(mktemp -d)" | |
| export HAPPIER_INSTALL_DIR="${HOME}/.happier" | |
| export HAPPIER_BIN_DIR="${HOME}/.local/bin" | |
| export HAPPIER_NONINTERACTIVE=1 | |
| export HAPPIER_NO_PATH_UPDATE=1 | |
| cp "apps/website/public/${{ steps.cli_tag.outputs.installer }}" /tmp/happier-install.sh | |
| chmod +x /tmp/happier-install.sh | |
| /tmp/happier-install.sh | |
| test -x "${HAPPIER_BIN_DIR}/happier" | |
| "${HAPPIER_BIN_DIR}/happier" --version | |
| "${HAPPIER_BIN_DIR}/happier" --help >/dev/null | |
| installers-smoke-macos: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }} | |
| name: Installer Smoke (macOS) | |
| runs-on: macos-latest | |
| timeout-minutes: 25 | |
| env: | |
| INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Verify published installers are synced | |
| run: node scripts/pipeline/run.mjs release-sync-installers --check | |
| - name: Detect CLI release tag | |
| id: cli_tag | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| tag="cli-stable" | |
| installer="install.sh" | |
| if [ "${INSTALLERS_CHANNEL}" = "preview" ]; then | |
| tag="cli-preview" | |
| installer="install-preview.sh" | |
| fi | |
| api="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" | |
| code="$(curl -sS -o /dev/null -w "%{http_code}" \ | |
| -H "Authorization: Bearer ${GITHUB_TOKEN}" \ | |
| -H "X-GitHub-Api-Version: 2022-11-28" \ | |
| "${api}" || true)" | |
| if [ "${code}" = "200" ]; then | |
| echo "tag_exists=true" >> "$GITHUB_OUTPUT" | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "installer=${installer}" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "tag_exists=false" >> "$GITHUB_OUTPUT" | |
| echo "Skipping installer smoke: ${tag} release tag not found (HTTP ${code})." | |
| - name: Run CLI installer from isolated temp location | |
| if: steps.cli_tag.outputs.tag_exists == 'true' | |
| env: | |
| HAPPIER_GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| export HOME="$(mktemp -d)" | |
| export HAPPIER_INSTALL_DIR="${HOME}/.happier" | |
| export HAPPIER_BIN_DIR="${HOME}/.local/bin" | |
| export HAPPIER_NONINTERACTIVE=1 | |
| export HAPPIER_NO_PATH_UPDATE=1 | |
| cp "apps/website/public/${{ steps.cli_tag.outputs.installer }}" /tmp/happier-install.sh | |
| chmod +x /tmp/happier-install.sh | |
| /tmp/happier-install.sh | |
| test -x "${HAPPIER_BIN_DIR}/happier" | |
| "${HAPPIER_BIN_DIR}/happier" --version | |
| "${HAPPIER_BIN_DIR}/happier" --help >/dev/null | |
| installers-smoke-windows: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_installers_smoke }} | |
| name: Installer Smoke (Windows) | |
| runs-on: windows-latest | |
| timeout-minutes: 25 | |
| env: | |
| INSTALLERS_CHANNEL: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.installers_channel || 'stable' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Verify published installers are synced | |
| shell: pwsh | |
| run: node scripts/pipeline/run.mjs release-sync-installers --check | |
| - name: Detect CLI release tag | |
| id: cli_tag | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| $ErrorActionPreference = "Continue" | |
| $tag = "cli-stable" | |
| $installer = "install.ps1" | |
| if ($env:INSTALLERS_CHANNEL -eq "preview") { | |
| $tag = "cli-preview" | |
| $installer = "install-preview.ps1" | |
| } | |
| $api = "https://api.github.com/repos/$env:GITHUB_REPOSITORY/releases/tags/$tag" | |
| $headers = @{ | |
| Authorization = "Bearer $env:GITHUB_TOKEN" | |
| "X-GitHub-Api-Version" = "2022-11-28" | |
| } | |
| $exists = "false" | |
| try { | |
| Invoke-RestMethod -Uri $api -Headers $headers | Out-Null | |
| $exists = "true" | |
| } catch { | |
| Write-Host "Skipping installer smoke: $tag release tag not found." | |
| } | |
| "tag_exists=$exists" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| "installer=$installer" | Out-File -FilePath $env:GITHUB_OUTPUT -Append | |
| - name: Run CLI installer from isolated temp location | |
| shell: pwsh | |
| if: steps.cli_tag.outputs.tag_exists == 'true' | |
| env: | |
| HAPPIER_GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| $env:HAPPIER_NONINTERACTIVE = "1" | |
| $env:HAPPIER_INSTALL_DIR = Join-Path $env:RUNNER_TEMP ".happier" | |
| $env:HAPPIER_BIN_DIR = Join-Path $env:RUNNER_TEMP ".local\bin" | |
| $scriptPath = Join-Path $env:RUNNER_TEMP "happier-install.ps1" | |
| Copy-Item -Path ("apps/website/public/" + "${{ steps.cli_tag.outputs.installer }}") -Destination $scriptPath -Force | |
| & $scriptPath | |
| $binaryPath = Join-Path $env:HAPPIER_BIN_DIR "happier.exe" | |
| if (-not (Test-Path $binaryPath)) { | |
| throw "Installer did not create $binaryPath" | |
| } | |
| & $binaryPath --version | |
| & $binaryPath --help | Out-Null | |
| binary-smoke: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_binary_smoke }} | |
| name: Binary Smoke (isolated cwd) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.5 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run isolated binary smoke tests | |
| run: | | |
| set -euo pipefail | |
| timeout --signal=KILL --kill-after=30s 25m node --test apps/stack/scripts/self_host_binary_smoke.integration.test.mjs | |
| timeout --signal=KILL --kill-after=30s 45m node --test apps/stack/scripts/release_binary_smoke.integration.test.mjs | |
| self-host-systemd-e2e: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_systemd }} | |
| name: Self-Host E2E (Linux systemd) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.5 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run real systemd self-host E2E | |
| run: | | |
| set -euo pipefail | |
| node --test apps/stack/scripts/self_host_systemd.real.integration.test.mjs | |
| self-host-launchd-e2e: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_launchd }} | |
| name: Self-Host E2E (macOS launchd) | |
| runs-on: macos-latest | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.5 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run real launchd self-host E2E | |
| run: | | |
| set -euo pipefail | |
| node --test apps/stack/scripts/self_host_launchd.real.integration.test.mjs | |
| self-host-schtasks-e2e: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_schtasks }} | |
| name: Self-Host E2E (Windows schtasks) | |
| runs-on: windows-latest | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.5 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run real schtasks self-host E2E | |
| run: node --test apps/stack/scripts/self_host_schtasks.real.integration.test.mjs | |
| self-host-daemon-e2e: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_self_host_daemon }} | |
| name: Self-Host + Daemon E2E | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: 1.3.5 | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run real self-host + daemon E2E | |
| run: node --test apps/stack/scripts/self_host_daemon.real.integration.test.mjs | |
| typecheck: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_typecheck }} | |
| name: Typecheck | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run governance self-tests | |
| run: yarn test:wiring:self && yarn test:policy:self | |
| - name: Run governance validators | |
| run: yarn test:wiring && yarn test:policy | |
| - name: Run governance inventory reports | |
| run: yarn test:inventory && yarn test:migration:inventory | |
| - name: Run typecheck | |
| run: yarn typecheck | |
| cli-daemon-e2e: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_cli_daemon_e2e }} | |
| name: CLI + Server (light/sqlite) E2E | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| env: | |
| PORT: "3005" | |
| HAPPIER_SERVER_URL: http://localhost:3005 | |
| HAPPIER_WEBAPP_URL: http://localhost:3005 | |
| HAPPIER_HOME_DIR: /tmp/happier-dev-test | |
| DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING: "true" | |
| HAPPIER_SERVER_LIGHT_DATA_DIR: /tmp/happier-server-light | |
| HAPPIER_DB_PROVIDER: sqlite | |
| HAPPY_DB_PROVIDER: sqlite | |
| # Fall back to a deterministic non-secret for forks / untrusted PRs (no access to repo secrets). | |
| HANDY_MASTER_SECRET: ${{ secrets.CI_MASTER_SECRET != '' && secrets.CI_MASTER_SECRET || 'happier-ci-master-secret-not-a-real-secret' }} | |
| HAPPIER_NO_BROWSER_OPEN: "1" | |
| TMPDIR: /tmp | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install system dependencies | |
| run: | | |
| export DEBIAN_FRONTEND=noninteractive | |
| APT_FLAGS="-o Acquire::Retries=3 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30" | |
| if [ "${ACT:-}" = "true" ] || [ -d /var/run/act ]; then | |
| . /etc/os-release || true | |
| CODENAME="${VERSION_CODENAME:-noble}" | |
| ARCH="$(dpkg --print-architecture)" | |
| BASE_URL="http://ports.ubuntu.com/ubuntu-ports" | |
| if [ "${ARCH}" = "amd64" ]; then BASE_URL="http://archive.ubuntu.com/ubuntu"; fi | |
| if command -v sudo >/dev/null 2>&1; then | |
| sudo rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true | |
| echo "deb ${BASE_URL} ${CODENAME} main universe" | sudo tee /etc/apt/sources.list >/dev/null | |
| else | |
| rm -f /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources || true | |
| echo "deb ${BASE_URL} ${CODENAME} main universe" > /etc/apt/sources.list | |
| fi | |
| fi | |
| if command -v sudo >/dev/null 2>&1; then | |
| sudo apt-get ${APT_FLAGS} update | |
| sudo apt-get ${APT_FLAGS} install -y --no-install-recommends tmux | |
| else | |
| apt-get ${APT_FLAGS} update | |
| apt-get ${APT_FLAGS} install -y --no-install-recommends tmux | |
| fi | |
| tmux -V | |
| - name: Prepare light DB (SQLite) + apply migrations | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: | | |
| set -euo pipefail | |
| yarn install --frozen-lockfile --ignore-engines | |
| yarn --cwd apps/server -s migrate:sqlite:deploy | |
| - name: Build CLI | |
| run: yarn workspace @happier-dev/cli build | |
| - name: Install provider CLI stubs (CI only) | |
| run: | | |
| set -euo pipefail | |
| trap 'exit 0' TERM INT | |
| STUB_BIN_DIR="/tmp/ci-bin" | |
| mkdir -p "${STUB_BIN_DIR}" | |
| write_stub() { | |
| local name="$1" | |
| local version="$2" | |
| cat > "${STUB_BIN_DIR}/${name}" <<EOF | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| case "\${1:-}" in | |
| --version|-v|version) | |
| echo "${version}" | |
| exit 0 | |
| ;; | |
| --help|-h|help) | |
| echo "${name} (ci stub)" | |
| exit 0 | |
| ;; | |
| esac | |
| # Some callers expect a long-running interactive process; do not exit. | |
| if [ -t 0 ]; then | |
| while true; do sleep 3600 & wait \$!; done | |
| else | |
| cat >/dev/null || true | |
| while true; do sleep 3600 & wait \$!; done | |
| fi | |
| EOF | |
| chmod +x "${STUB_BIN_DIR}/${name}" | |
| } | |
| write_stub "auggie" "0.0.0-ci-stub" | |
| write_stub "claude" "0.0.0-ci-stub" | |
| write_stub "codex" "0.0.0-ci-stub" | |
| write_stub "gemini" "0.0.0-ci-stub" | |
| write_stub "opencode" "0.0.0-ci-stub" | |
| echo "${STUB_BIN_DIR}" >> "${GITHUB_PATH}" | |
| export PATH="${STUB_BIN_DIR}:${PATH}" | |
| command -v auggie && auggie --version | |
| command -v claude && claude --version | |
| command -v codex && codex --version | |
| command -v gemini && gemini --version | |
| command -v opencode && opencode --version | |
| - name: Run daemon integration suite (with light server) | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "${HAPPIER_SERVER_LIGHT_DATA_DIR}" | |
| nohup yarn --cwd apps/server start:light > "/tmp/happier-server-light.log" 2>&1 & | |
| SERVER_PID="$(jobs -p | tail -n 1 || true)" | |
| if [ -z "${SERVER_PID}" ]; then | |
| echo "Failed to start server process" | |
| tail -n 200 "/tmp/happier-server-light.log" || true | |
| exit 1 | |
| fi | |
| echo "${SERVER_PID}" > "/tmp/happier-server-light.pid" | |
| cleanup() { | |
| if [ -n "${SERVER_PID:-}" ]; then | |
| kill "${SERVER_PID}" || true | |
| fi | |
| } | |
| trap cleanup EXIT | |
| for i in $(seq 1 60); do | |
| if curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then | |
| # Require health to be stable (avoid flaking on a transient accept before the server is ready). | |
| sleep 1 | |
| curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1 | |
| echo "Server is healthy (stable)" | |
| break | |
| fi | |
| sleep 1 | |
| done | |
| if ! curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then | |
| echo "Server failed to become healthy within timeout" | |
| tail -n 200 "/tmp/happier-server-light.log" || true | |
| exit 1 | |
| fi | |
| # Create a real account + token via the normal `/v1/auth` flow (ed25519 signature), | |
| # then write the credentials file expected by `.env.integration-test`. | |
| node scripts/pipeline/run.mjs testing-create-auth-credentials | |
| yarn --cwd apps/cli -s vitest run --config vitest.integration.config.ts src/daemon/daemon.integration.test.ts | |
| - name: Dump server logs (on failure) | |
| if: failure() | |
| run: tail -n 300 "/tmp/happier-server-light.log" || true | |
| - name: Dump daemon logs (on failure) | |
| if: failure() | |
| run: | | |
| set -euo pipefail | |
| ROOT="${HAPPIER_HOME_DIR:-$HOME/.happier-dev-test}" | |
| echo "HAPPIER_HOME_DIR=${ROOT}" | |
| echo "--- daemon.state.json candidates (redacted) ---" | |
| find "$ROOT" -maxdepth 4 -type f -name 'daemon.state.json' -print | head -n 10 || true | |
| find "$ROOT" -maxdepth 4 -type f -name 'daemon.state.json' -print | head -n 10 | while read -r f; do | |
| node --input-type=module - <<'NODE' "$f" || true | |
| import fs from "node:fs"; | |
| const file = process.argv[1]; | |
| const raw = fs.readFileSync(file, "utf8"); | |
| const data = JSON.parse(raw); | |
| const redacted = { | |
| pid: data?.pid, | |
| httpPort: data?.httpPort, | |
| startedAt: data?.startedAt, | |
| startedWithCliVersion: data?.startedWithCliVersion, | |
| daemonLogPath: data?.daemonLogPath, | |
| }; | |
| console.log(`--- ${file} (redacted) ---`); | |
| console.log(JSON.stringify(redacted, null, 2)); | |
| NODE | |
| done | |
| echo "--- logs (match scan) ---" | |
| MATCH_PATTERN="SESSION_WEBHOOK_TIMEOUT|CHILD_EXITED_BEFORE_WEBHOOK|SPAWN_FAILED|Unauthorized|EADDRINUSE|ECONN|fetch failed|Request failed|HTTP 5[0-9]{2}|Deferred awaiter resolution|Current tracked sessions before webhook|Registered externally-started session" | |
| find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print0 2>/dev/null \ | |
| | xargs -0 -r grep -nE "${MATCH_PATTERN}" \ | |
| | tail -n 400 || true | |
| echo "--- logs (matched files tail) ---" | |
| find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print0 2>/dev/null \ | |
| | xargs -0 -r grep -lE "${MATCH_PATTERN}" \ | |
| | head -n 20 | while read -r f; do | |
| echo "=== tail: $f ===" | |
| tail -n 200 "$f" || true | |
| echo | |
| done | |
| echo "--- logs (tail) ---" | |
| find "$ROOT" -maxdepth 6 -type f \( -name '*-daemon.log' -o -name '*.log' \) -print 2>/dev/null \ | |
| | xargs -r ls -1t | head -n 16 | while read -r f; do | |
| echo "=== tail: $f ===" | |
| echo "--- error scan: $f ---" | |
| # Avoid relying on ripgrep being installed; grep is always available. | |
| grep -nE "SESSION_WEBHOOK_TIMEOUT|SPAWN_FAILED|Unauthorized|EADDRINUSE|ECONN|fetch failed|Request failed|HTTP 5[0-9]{2}" "$f" | tail -n 200 || true | |
| echo | |
| tail -n 200 "$f" || true | |
| echo | |
| done | |
| e2e-core: | |
| if: ${{ (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call') || inputs.run_e2e_core }} | |
| name: Core E2E (fast, packages/tests, server-light, sqlite) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install Sapling | |
| run: | | |
| bash scripts/ci/install_sapling_ubuntu22.sh | |
| - name: Verify Sapling CLI | |
| run: sl --version | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run core e2e suite | |
| env: | |
| CI: "1" | |
| HAPPIER_E2E_SAVE_ARTIFACTS: "0" | |
| HAPPIER_E2E_DB_PROVIDER: sqlite | |
| run: yarn test:e2e:core:fast | |
| - name: Upload e2e artifacts (on failure) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: core-e2e-artifacts-fast-sqlite | |
| path: .project/logs/e2e | |
| e2e-core-slow: | |
| if: ${{ github.event_name == 'workflow_call' && inputs.run_e2e_core_slow }} | |
| name: Core E2E (slow, packages/tests, server-light, sqlite) | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node (GitHub Actions) | |
| if: env.ACT != 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Setup Node (act) | |
| if: env.ACT == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install Sapling | |
| run: | | |
| bash scripts/ci/install_sapling_ubuntu22.sh | |
| - name: Verify Sapling CLI | |
| run: sl --version | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run core e2e suite (slow) | |
| env: | |
| CI: "1" | |
| HAPPIER_E2E_SAVE_ARTIFACTS: "0" | |
| HAPPIER_E2E_DB_PROVIDER: sqlite | |
| run: yarn test:e2e:core:slow | |
| - name: Upload e2e artifacts (on failure) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: core-e2e-artifacts-slow-sqlite | |
| path: .project/logs/e2e | |
| release_actor_guard: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_providers }} | |
| name: Release actor guard | |
| permissions: | |
| contents: read | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Authorize release actor | |
| uses: ./.github/actions/release-actor-guard | |
| with: | |
| team_slug: release-admins | |
| app_id: ${{ secrets.RELEASE_BOT_APP_ID }} | |
| private_key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} | |
| providers: | |
| needs: [release_actor_guard] | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_providers }} | |
| name: Providers (contract matrix) | |
| runs-on: ubuntu-latest | |
| environment: providers-ci | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Install provider CLIs | |
| env: | |
| PRESET: ${{ inputs.providers_preset }} | |
| OPENCODE_SPEC: ${{ inputs.providers_opencode }} | |
| run: | | |
| set -euo pipefail | |
| need_claude=0 | |
| need_codex=0 | |
| need_opencode=0 | |
| case "$PRESET" in | |
| all) need_claude=1; need_codex=1; need_opencode=1 ;; | |
| claude) need_claude=1 ;; | |
| codex) need_codex=1 ;; | |
| opencode) need_opencode=1 ;; | |
| *) echo "Unknown providers_preset: $PRESET" >&2; exit 2 ;; | |
| esac | |
| if [ "$need_claude" = "1" ]; then | |
| npm install -g @anthropic-ai/claude-code | |
| claude --version | |
| fi | |
| if [ "$need_codex" = "1" ]; then | |
| npm install -g @openai/codex | |
| codex --version | |
| fi | |
| if [ "$need_opencode" = "1" ]; then | |
| # Avoid `curl ... | bash` by downloading a pinned, checksummed release artifact. | |
| opencode_version="${OPENCODE_SPEC%%|*}" | |
| opencode_sha256="" | |
| if [ "$opencode_version" != "$OPENCODE_SPEC" ]; then | |
| opencode_sha256="${OPENCODE_SPEC#*|}" | |
| fi | |
| version="${opencode_version#v}" | |
| asset="opencode-linux-x64-baseline.tar.gz" | |
| url="https://github.com/anomalyco/opencode/releases/download/v${version}/${asset}" | |
| install_dir="$HOME/.opencode/bin" | |
| mkdir -p "$install_dir" | |
| tmp_dir="$(mktemp -d)" | |
| curl -fsSL -o "$tmp_dir/$asset" "$url" | |
| if [ -n "${opencode_sha256:-}" ]; then | |
| echo "${opencode_sha256} $tmp_dir/$asset" | sha256sum -c - | |
| fi | |
| tar -xzf "$tmp_dir/$asset" -C "$tmp_dir" | |
| mv -f "$tmp_dir/opencode" "$install_dir/opencode" | |
| chmod 755 "$install_dir/opencode" | |
| rm -rf "$tmp_dir" | |
| echo "$install_dir" >> "$GITHUB_PATH" | |
| export PATH="$install_dir:$PATH" | |
| opencode --version | |
| fi | |
| - name: Validate provider auth env | |
| env: | |
| PRESET: ${{ inputs.providers_preset }} | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| run: | | |
| set -euo pipefail | |
| need_openai=0 | |
| need_anthropic=0 | |
| case "$PRESET" in | |
| all) need_openai=1; need_anthropic=1 ;; | |
| codex) need_openai=1 ;; | |
| opencode) need_openai=1 ;; | |
| claude) need_anthropic=1 ;; | |
| *) echo "Unknown providers_preset: $PRESET" >&2; exit 2 ;; | |
| esac | |
| if [ "$need_openai" = "1" ] && [ -z "${OPENAI_API_KEY:-}" ] && [ -z "${CODEX_API_KEY:-}" ]; then | |
| echo "Missing provider secrets: set OPENAI_API_KEY (or CODEX_API_KEY) to run preset=$PRESET" >&2 | |
| exit 1 | |
| fi | |
| if [ "$need_anthropic" = "1" ] && [ -z "${ANTHROPIC_API_KEY:-}" ]; then | |
| echo "Missing provider secrets: set ANTHROPIC_API_KEY to run preset=$PRESET" >&2 | |
| exit 1 | |
| fi | |
| - name: Run provider contract suite | |
| env: | |
| CI: "1" | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| CODEX_API_KEY: ${{ secrets.CODEX_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| HAPPIER_E2E_PROVIDER_MODEL: ${{ fromJSON(inputs.providers_models).default }} | |
| HAPPY_E2E_PROVIDER_MODEL: ${{ fromJSON(inputs.providers_models).default }} | |
| HAPPIER_E2E_PROVIDER_MODELS: ${{ fromJSON(inputs.providers_models).overrides }} | |
| HAPPY_E2E_PROVIDER_MODELS: ${{ fromJSON(inputs.providers_models).overrides }} | |
| run: | | |
| set -euo pipefail | |
| flags=() | |
| if [ "${{ fromJSON(inputs.providers_flags).strict_keys }}" = "true" ]; then flags+=("--strict-keys"); fi | |
| if [ "${{ fromJSON(inputs.providers_flags).flake_retry }}" != "true" ]; then flags+=("--no-flake-retry"); fi | |
| if [ "${{ fromJSON(inputs.providers_flags).update_baselines }}" = "true" ]; then flags+=("--update-baselines"); fi | |
| yarn workspace @happier-dev/tests providers:run "${{ inputs.providers_preset }}" "${{ inputs.providers_tier }}" "${flags[@]}" | |
| - name: Upload provider artifacts (on failure) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: providers-artifacts | |
| path: .project/logs/e2e | |
| stress: | |
| if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.run_stress }} | |
| name: Stress (repeat/chaos) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 120 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22.x | |
| cache: yarn | |
| cache-dependency-path: yarn.lock | |
| - name: Enable Corepack | |
| run: | | |
| corepack enable | |
| corepack prepare yarn@1.22.22 --activate | |
| - name: Install dependencies | |
| env: | |
| YARN_PRODUCTION: "false" | |
| npm_config_production: "false" | |
| run: yarn install --frozen-lockfile --ignore-engines | |
| - name: Run stress suite | |
| env: | |
| CI: "1" | |
| HAPPIER_E2E_REPEAT: ${{ fromJSON(inputs.stress_config).repeat }} | |
| HAPPIER_E2E_SEED: ${{ fromJSON(inputs.stress_config).seed }} | |
| HAPPIER_E2E_FLAKE_RETRY: "1" | |
| run: yarn test:stress | |
| - name: Upload stress artifacts (on failure) | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: stress-artifacts | |
| path: .project/logs/e2e |