chore: remove upstream Codespaces workflow #9
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: MCP CI | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| name: Determine need to run MCP checks | |
| outputs: | |
| mcp: ${{ steps.filter.outputs.mcp || 'true' }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # Not only the MCP folder but any file that generates schema for the mcp folder | |
| - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| id: filter | |
| if: github.event_name != 'push' # Run all tests on master push | |
| with: | |
| filters: | | |
| mcp: | |
| - 'services/mcp/**' | |
| - 'products/**' | |
| - 'ee/**/*' | |
| - 'posthog/**/*' | |
| - '.github/workflows/ci-mcp.yml' | |
| - '.github/workflows/mcp-publish.yml' | |
| build: | |
| name: Build Package | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: changes | |
| if: needs.changes.outputs.mcp == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: 3.12.12 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0 | |
| with: | |
| version: '0.10.2' # pinned: unpinned setup-uv calls GH API on every job, exhausts rate limit | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.ref == 'refs/heads/master' }} | |
| - name: Install Python dependencies | |
| run: UV_PROJECT_ENVIRONMENT=$pythonLocation uv sync --frozen --dev | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile | |
| - name: Build package | |
| run: cd services/mcp && pnpm build | |
| - name: Type check | |
| run: cd services/mcp && pnpm typecheck | |
| - name: Check MCP schema is up to date | |
| run: | | |
| ./bin/hogli build:schema-mcp | |
| if ! git diff --exit-code services/mcp/schema/tool-inputs.json; then | |
| echo "" | |
| echo "::error::MCP tool-inputs.json is out of date. Run 'hogli build:schema-mcp' and commit the result." | |
| exit 1 | |
| fi | |
| - name: Check generated UI apps are up to date | |
| run: | | |
| cd services/mcp && pnpm run generate:ui-apps | |
| if ! git diff --exit-code -- src/ui-apps/apps/generated/ src/resources/ui-apps.generated.ts; then | |
| echo "" | |
| echo "::warning::The following generated files are out of date:" | |
| git diff --name-only -- src/ui-apps/apps/generated/ src/resources/ui-apps.generated.ts | |
| echo "" | |
| echo "::error::Generated UI app files are out of date. Run 'pnpm --filter=@posthog/mcp run generate:ui-apps' and commit the result." | |
| exit 1 | |
| fi | |
| - name: Lint tool names | |
| run: cd services/mcp && pnpm lint-tool-names | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: changes | |
| if: needs.changes.outputs.mcp == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile | |
| - name: Run unit tests | |
| run: | | |
| pnpm --filter=@posthog/mcp run test -u | |
| if [ -n "$(git status --porcelain -- services/mcp/tests/unit)" ]; then | |
| echo "" | |
| git status --short -- services/mcp/tests/unit | |
| echo "::error::MCP unit test artifacts are out of date. Run 'pnpm --filter=@posthog/mcp run test -u' and commit the result." | |
| exit 1 | |
| fi | |
| - name: Create snapshot patch | |
| id: create-snapshot-patch | |
| if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.actor, '[bot]') && github.actor != 'posthog-bot' && !contains(github.actor, 'github-actions') }} | |
| run: | | |
| SNAPSHOT_PATH="services/mcp/tests/unit" | |
| if [ -z "$(git status --porcelain -- "$SNAPSHOT_PATH")" ]; then | |
| echo "No MCP unit test changes" | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "MCP unit test changes detected:" | |
| git status --short -- "$SNAPSHOT_PATH" | |
| git add -N "$SNAPSHOT_PATH" | |
| mkdir -p /tmp/patches | |
| git diff --binary --full-index "$SNAPSHOT_PATH" > /tmp/patches/mcp-unit-tests.patch | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Upload snapshot patch | |
| if: ${{ steps.create-snapshot-patch.outputs.has-changes == 'true' && always() }} | |
| uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 | |
| with: | |
| name: mcp-snapshot-patch | |
| path: /tmp/patches/mcp-unit-tests.patch | |
| retention-days: 1 | |
| if-no-files-found: ignore | |
| integration-tests: | |
| name: Integration Tests | |
| runs-on: depot-ubuntu-latest-4 | |
| needs: changes | |
| timeout-minutes: 30 | |
| if: needs.changes.outputs.mcp == 'true' | |
| env: | |
| OPT_OUT_CAPTURE: 1 | |
| SECRET_KEY: '6b01eee4f945ca25045b5aab440b953461faf08693a9abbf1166dc7c6b9772da' | |
| DATABASE_URL: 'postgres://posthog:posthog@localhost:5432/posthog' | |
| PERSONS_DB_WRITER_URL: 'postgres://posthog:posthog@localhost:5432/posthog_persons' | |
| REDIS_URL: 'redis://localhost' | |
| CLICKHOUSE_HOST: 'localhost' | |
| CLICKHOUSE_SECURE: 'False' | |
| CLICKHOUSE_VERIFY: 'False' | |
| OBJECT_STORAGE_ENABLED: 'True' | |
| OBJECT_STORAGE_ENDPOINT: 'http://localhost:19000' | |
| OBJECT_STORAGE_ACCESS_KEY_ID: 'object_storage_root_user' | |
| OBJECT_STORAGE_SECRET_ACCESS_KEY: 'object_storage_root_password' | |
| SKIP_SERVICE_VERSION_REQUIREMENTS: 1 | |
| NO_RESTART_LOOP: 1 | |
| DISABLE_SECURE_SSL_REDIRECT: 'True' | |
| CLICKHOUSE_LOGS_CLUSTER_SECURE: 'False' | |
| CELERY_METRICS_PORT: 8999 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| - name: Clean up data directories with container permissions | |
| run: | | |
| [ -d "data" ] && docker run --rm -v "$(pwd)/data:/data" alpine sh -c "rm -rf /data/seaweedfs /data/minio" || true | |
| continue-on-error: true | |
| - name: Stop/Start stack with Docker Compose | |
| shell: bash | |
| run: | | |
| cp posthog/user_scripts/latest_user_defined_function.xml docker/clickhouse/user_defined_function.xml | |
| ( | |
| max_attempts=3 | |
| attempt=1 | |
| delay=5 | |
| while [ $attempt -le $max_attempts ]; do | |
| echo "Attempt $attempt of $max_attempts to start stack..." | |
| if docker compose -f docker-compose.dev.yml down && \ | |
| docker compose -f docker-compose.dev.yml -f docker-compose.profiles.yml up -d; then | |
| echo "Stack started successfully" | |
| exit 0 | |
| fi | |
| echo "Failed to start stack on attempt $attempt" | |
| if [ $attempt -lt $max_attempts ]; then | |
| sleep_time=$((delay * 2 ** (attempt - 1))) | |
| echo "Waiting ${sleep_time} seconds before retry..." | |
| sleep $sleep_time | |
| fi | |
| attempt=$((attempt + 1)) | |
| done | |
| echo "Failed to start stack after $max_attempts attempts" | |
| exit 1 | |
| ) & | |
| - name: Add service hostnames to /etc/hosts | |
| shell: bash | |
| run: echo "127.0.0.1 db redis7 kafka clickhouse clickhouse-coordinator objectstorage seaweedfs temporal" | sudo tee -a /etc/hosts | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: 3.12.12 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0 | |
| with: | |
| version: '0.10.2' # pinned: unpinned setup-uv calls GH API on every job, exhausts rate limit | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| save-cache: ${{ github.ref == 'refs/heads/master' }} | |
| - name: Install SAML (python3-saml) dependencies | |
| shell: bash | |
| run: | | |
| sudo apt-get update && sudo apt-get install libxml2-dev libxmlsec1-dev libxmlsec1-openssl | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Fix node-gyp permissions | |
| run: chmod +x ~/setup-pnpm/node_modules/.pnpm/pnpm@*/node_modules/pnpm/dist/node_modules/node-gyp/gyp/gyp_main.py | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: 'pnpm' | |
| - name: Install Python dependencies | |
| shell: bash | |
| run: | | |
| UV_PROJECT_ENVIRONMENT=$pythonLocation uv sync --frozen --dev | |
| - name: Install Node dependencies | |
| run: pnpm --filter=@posthog/mcp... --filter='./products/*' install --frozen-lockfile | |
| - name: Set up needed files | |
| shell: bash | |
| run: | | |
| mkdir -p frontend/dist | |
| touch frontend/dist/index.html | |
| touch frontend/dist/layout.html | |
| touch frontend/dist/exporter.html | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e | |
| with: | |
| toolchain: 1.91.1 | |
| components: cargo | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 | |
| with: | |
| shared-key: 'v2-rust-mcp' | |
| workspaces: rust | |
| save-if: ${{ github.ref == 'refs/heads/master' }} | |
| - name: Install sqlx-cli | |
| run: | | |
| cargo install sqlx-cli --version 0.8.0 --features postgres --no-default-features --locked | |
| - name: Start Docker services | |
| shell: bash | |
| env: | |
| COMPOSE_FILE: docker-compose.dev.yml:docker-compose.profiles.yml | |
| run: bin/ci-wait-for-docker launch --down | |
| - name: Wait for Docker services | |
| shell: bash | |
| env: | |
| COMPOSE_FILE: docker-compose.dev.yml:docker-compose.profiles.yml | |
| run: bin/ci-wait-for-docker wait | |
| - name: Run migrations | |
| run: | | |
| # Create persons database and run sqlx migrations first | |
| DATABASE_URL="postgres://posthog:posthog@localhost:5432/posthog_persons" \ | |
| sqlx database create | |
| DATABASE_URL="postgres://posthog:posthog@localhost:5432/posthog_persons" \ | |
| sqlx migrate run --source rust/persons_migrations/ | |
| # Then run Django migrations | |
| python manage.py migrate --noinput | |
| python manage.py migrate_clickhouse | |
| - name: Seed test data | |
| run: python manage.py generate_demo_data --n-clusters 10 --days-past 7 --days-future 0 --skip-materialization --skip-flag-sync --skip-user-product-list | |
| - name: Create API key and extract test IDs | |
| run: | | |
| python -c " | |
| import django, os | |
| os.environ['DJANGO_SETTINGS_MODULE'] = 'posthog.settings' | |
| django.setup() | |
| from posthog.models import Organization, Team, User, EventDefinition | |
| from posthog.models.personal_api_key import PersonalAPIKey | |
| from posthog.models.utils import hash_key_value | |
| org = Organization.objects.first() | |
| team = Team.objects.first() | |
| user = User.objects.first() | |
| PersonalAPIKey.objects.create( | |
| user=user, | |
| label='mcp_ci_api_key', | |
| secure_value=hash_key_value('e2e_demo_api_key'), | |
| scopes=['*'], | |
| ) | |
| # Ensure common event definitions exist for MCP integration tests | |
| for event_name in ['\$pageview', '\$pageleave', '\$autocapture', '\$screen']: | |
| EventDefinition.objects.get_or_create(name=event_name, team=team) | |
| print(f'TEST_ORG_ID={org.id}') | |
| print(f'TEST_PROJECT_ID={team.id}') | |
| " >> $GITHUB_ENV | |
| - name: Source celery queues | |
| run: | | |
| source ./bin/celery-queues.env | |
| echo "CELERY_WORKER_QUEUES=$CELERY_WORKER_QUEUES" >> $GITHUB_ENV | |
| - name: Start PostHog web & Celery worker | |
| run: | | |
| python manage.py run_autoreload_celery --type=worker &> /tmp/celery.log & | |
| python -m granian --interface asgi posthog.asgi:application --host 0.0.0.0 --port 8000 --workers 1 &> /tmp/server.log & | |
| - name: Wait for PostHog to be ready | |
| uses: iFaxity/wait-on-action@1fe019e0475491e9e8c4f421b6914ccc3ed8f99c # v1.2.1 | |
| with: | |
| resource: http://localhost:8000 | |
| timeout: 180000 | |
| interval: 2000 | |
| verbose: true | |
| - name: Run integration tests | |
| run: cd services/mcp && pnpm run test:integration | |
| env: | |
| TEST_POSTHOG_API_BASE_URL: http://localhost:8000 | |
| TEST_POSTHOG_PERSONAL_API_KEY: e2e_demo_api_key | |
| TEST_ORG_ID: ${{ env.TEST_ORG_ID }} | |
| TEST_PROJECT_ID: ${{ env.TEST_PROJECT_ID }} | |
| - name: Show server logs on failure | |
| if: failure() | |
| run: cat /tmp/server.log || true | |
| - name: Show celery logs on failure | |
| if: failure() | |
| run: cat /tmp/celery.log || true | |
| - name: Show docker compose logs on failure | |
| if: failure() | |
| shell: bash | |
| run: docker compose -f docker-compose.dev.yml logs | |
| handle-snapshots: | |
| name: Commit snapshot changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [changes, unit-tests] | |
| if: ${{ always() && needs.changes.outputs.mcp == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && !contains(github.actor, '[bot]') && github.actor != 'posthog-bot' && !contains(github.actor, 'github-actions') }} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Get app token | |
| id: app-token | |
| uses: getsentry/action-github-app-token@d4b5da6c5e37703f8c3b3e43abb5705b46e159cc # v3.0.0 | |
| with: | |
| app_id: ${{ secrets.GH_APP_POSTHOG_TESTS_APP_ID }} | |
| private_key: ${{ secrets.GH_APP_POSTHOG_TESTS_PRIVATE_KEY }} | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha }} | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| token: ${{ steps.app-token.outputs.token }} | |
| fetch-depth: 1 | |
| - name: Download snapshot patches | |
| id: download-patches | |
| continue-on-error: true | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 | |
| with: | |
| pattern: mcp-snapshot-patch | |
| path: /tmp/snapshot-patches | |
| merge-multiple: true | |
| - name: Check for patches | |
| id: check-patches | |
| run: | | |
| if [ "${{ steps.download-patches.outcome }}" == "failure" ] || [ ! -d /tmp/snapshot-patches ]; then | |
| echo "has-patches=false" >> $GITHUB_OUTPUT | |
| echo "No snapshot patches found" | |
| exit 0 | |
| fi | |
| if [ -n "$(find /tmp/snapshot-patches -name '*.patch' -type f -size +0c 2>/dev/null)" ]; then | |
| echo "has-patches=true" >> $GITHUB_OUTPUT | |
| echo "Found patches:" | |
| ls -la /tmp/snapshot-patches/ | |
| else | |
| echo "has-patches=false" >> $GITHUB_OUTPUT | |
| echo "Patch files empty or missing - no snapshot changes" | |
| fi | |
| - name: Commit snapshots | |
| if: steps.check-patches.outputs.has-patches == 'true' | |
| uses: ./.github/actions/commit-snapshots | |
| with: | |
| workflow-type: backend | |
| patch-path: /tmp/snapshot-patches | |
| snapshot-path: 'services/mcp/tests/unit/' | |
| commit-message: 'test(mcp): update unit test snapshots' | |
| pr-number: ${{ github.event.pull_request.number }} | |
| repository: ${{ github.repository }} | |
| commit-sha: ${{ github.event.pull_request.head.sha }} | |
| branch-name: ${{ github.event.pull_request.head.ref }} | |
| github-token: ${{ steps.app-token.outputs.token }} | |
| # Collation job for required status check | |
| mcp_tests: | |
| needs: [changes, build, unit-tests, integration-tests, handle-snapshots] | |
| name: MCP Tests Pass | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: always() | |
| steps: | |
| - name: Check all MCP jobs | |
| run: | | |
| # Fail if change detection itself failed | |
| if [[ "${{ needs.changes.result }}" == "failure" ]]; then | |
| echo "Change detection job failed." | |
| exit 1 | |
| fi | |
| # Pass if no MCP changes detected (jobs were skipped) | |
| if [[ "${{ needs.changes.outputs.mcp }}" != "true" ]]; then | |
| echo "MCP checks were skipped (no relevant changes)." | |
| exit 0 | |
| fi | |
| if [[ "${{ needs.build.result }}" != "success" && "${{ needs.build.result }}" != "skipped" ]]; then | |
| echo "MCP build failed." | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.unit-tests.result }}" != "success" && "${{ needs.unit-tests.result }}" != "skipped" ]]; then | |
| echo "MCP unit tests failed." | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.integration-tests.result }}" != "success" && "${{ needs.integration-tests.result }}" != "skipped" ]]; then | |
| echo "MCP integration tests failed." | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.handle-snapshots.result }}" == "failure" ]]; then | |
| echo "MCP snapshot commit job failed." | |
| exit 1 | |
| fi | |
| echo "All MCP checks passed." |