feat(code): multi-repo support for cloud tasks #9897
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: Run unit and integration tests | |
| on: | |
| pull_request: | |
| concurrency: | |
| group: test-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| workflow: ${{ steps.filter.outputs.workflow }} | |
| packages: ${{ steps.filter.outputs.packages }} | |
| steps: | |
| - name: Detect relevant changes | |
| id: filter | |
| uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| with: | |
| predicate-quantifier: every | |
| filters: | | |
| # Wider excludes than code-quality.yml, which keeps .github/scripts | |
| # and .vscode/.claude in scope because Biome lints them. | |
| code: | |
| - "!**/*.md" | |
| - "!docs/**" | |
| - "!.github/**" | |
| - "!.vscode/**" | |
| - "!.claude/**" | |
| - "!.husky/**" | |
| - "!LICENSE" | |
| - "!.gitignore" | |
| - "!.env.example" | |
| workflow: | |
| - ".github/workflows/test.yml" | |
| packages: | |
| - "packages/**" | |
| unit-test: | |
| needs: changes | |
| # Fail closed: if change detection itself failed, run instead of skipping. | |
| if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.code == 'true' || needs.changes.outputs.workflow == 'true') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Rebuild native modules for Node | |
| run: node scripts/rebuild-better-sqlite3-node.mjs | |
| - name: Build dependencies | |
| run: pnpm --filter @posthog/electron-trpc build | |
| - name: Run tests | |
| run: pnpm test | |
| - name: Upload test results to Trunk | |
| # Run even when tests fail so flaky/failed results are still reported, | |
| # but never let an upload problem fail the job. | |
| if: ${{ !cancelled() }} | |
| continue-on-error: true | |
| uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 | |
| with: | |
| # Scope to each package root. A bare **/junit.xml glob descends into | |
| # node_modules, where pnpm symlinks workspace packages, and uploads | |
| # every report many times over. | |
| junit-paths: "apps/*/junit.xml,packages/*/junit.xml" | |
| org-slug: posthog-inc | |
| token: ${{ secrets.TRUNK_API_TOKEN }} | |
| integration-test: | |
| needs: changes | |
| # Fail closed: if change detection itself failed, run instead of skipping. | |
| if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.code == 'true' || needs.changes.outputs.workflow == 'true') }} | |
| runs-on: macos-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| id: playwright-cache | |
| with: | |
| path: ~/Library/Caches/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: | | |
| playwright-${{ runner.os }}- | |
| - name: Cache Electron binary | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/Library/Caches/electron | |
| key: electron-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: | | |
| electron-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Rebuild better-sqlite3 for Electron | |
| run: node scripts/rebuild-better-sqlite3-electron.mjs | |
| - name: Build packages | |
| run: | | |
| pnpm --filter @posthog/electron-trpc build & | |
| pnpm --filter @posthog/platform build & | |
| pnpm --filter @posthog/shared build | |
| pnpm --filter @posthog/git build | |
| pnpm --filter @posthog/enricher build | |
| pnpm --filter agent build & | |
| wait | |
| - name: Package Electron app | |
| run: pnpm --filter code run package | |
| env: | |
| NODE_OPTIONS: "--max-old-space-size=6144" | |
| - name: Install Playwright | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter code exec playwright install --with-deps chromium | |
| - name: Run E2E smoke tests | |
| run: pnpm --filter code run test:e2e | |
| env: | |
| CI: true | |
| - name: Upload test results to Trunk | |
| # Run even when E2E tests fail so flaky/failed results are still | |
| # reported, but never let an upload problem fail the job. | |
| if: ${{ !cancelled() }} | |
| continue-on-error: true | |
| uses: trunk-io/analytics-uploader@385f1ccdf345b4532dc4b6c665dd432b702b8e28 # v2.1.2 | |
| with: | |
| junit-paths: "apps/code/tests/e2e/junit.xml" | |
| org-slug: posthog-inc | |
| token: ${{ secrets.TRUNK_API_TOKEN }} | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| if: failure() | |
| with: | |
| name: playwright-report | |
| path: apps/code/playwright-report/ | |
| retention-days: 7 | |
| e2e: | |
| # Live-model e2e for the @posthog/agent adapters (claude + codex). Runs only | |
| # after the unit + integration jobs pass — a red tree never reaches the | |
| # gateway — and only when a packages/ file changed. Each arm still self-skips | |
| # unless the POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY secret is present (fork PRs never | |
| # see it) and POSTHOG_CODE_E2E_GATEWAY_URL points at a runner-reachable gateway. | |
| # Drives cheap models (claude-haiku-4-5 / gpt-5-mini), so a run is a handful of | |
| # short turns. | |
| needs: [changes, unit-test, integration-test] | |
| # Always run on any packages/ change (or a change to this workflow), once | |
| # unit-test + integration-test have passed. There is intentionally NO gate on | |
| # the gateway config being present: if the POSTHOG_CODE_E2E_* secret/vars are | |
| # missing, the fail-loud guard (packages/agent/e2e/guard.e2e.test.ts) REDS the | |
| # run rather than skipping to green. Only fork PRs are skipped, since they never | |
| # receive the gateway secret and would red spuriously. | |
| if: ${{ (needs.changes.outputs.packages == 'true' || needs.changes.outputs.workflow == 'true') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: 22 | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build agent dependencies | |
| run: | | |
| pnpm --filter @posthog/shared run build | |
| pnpm --filter @posthog/git run build | |
| pnpm --filter @posthog/enricher run build | |
| - name: Download native codex binary | |
| # Non-fatal at the STEP so a failure surfaces as the fail-loud binary guard | |
| # (guard.e2e.test.ts) with a clear message rather than an opaque download | |
| # error. A missing binary then REDS the run (the guard fails when a token is | |
| # set) instead of letting the codex arm silently skip to green. | |
| run: node apps/code/scripts/download-binaries.mjs || echo "codex binary download failed; the binary guard test will red the run" | |
| - name: Run live e2e (both adapters) | |
| run: pnpm --filter agent run test:e2e | |
| # The suite reads these POSTHOG_CODE_E2E_* names directly | |
| # (packages/agent/e2e/config.ts). The personal API key is an org secret; the | |
| # URL and model/environment overrides are org vars. | |
| env: | |
| POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY: ${{ secrets.POSTHOG_CODE_E2E_GATEWAY_PERSONAL_API_KEY }} | |
| # Accept the URL from either an org var or an org secret — it has ended | |
| # up in both places, and vars.* can't see secrets. | |
| POSTHOG_CODE_E2E_GATEWAY_URL: ${{ vars.POSTHOG_CODE_E2E_GATEWAY_URL || secrets.POSTHOG_CODE_E2E_GATEWAY_URL }} | |
| POSTHOG_CODE_E2E_CLAUDE_MODEL: ${{ vars.POSTHOG_CODE_E2E_CLAUDE_MODEL }} | |
| POSTHOG_CODE_E2E_CODEX_MODEL: ${{ vars.POSTHOG_CODE_E2E_CODEX_MODEL }} | |
| # Optional: set vars.POSTHOG_CODE_E2E_ENVIRONMENT=cloud to exercise the | |
| # cloud code path (sandbox/permission-profile gating). Unset = local. The | |
| # OS-sandbox enforcement test is macOS-gated, so it doesn't red this linux | |
| # runner. | |
| POSTHOG_CODE_E2E_ENVIRONMENT: ${{ vars.POSTHOG_CODE_E2E_ENVIRONMENT }} |