feat: add deterministic type: mcp workflow step #1162
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
| # Continuous Integration workflow for Conductor | |
| # | |
| # Runs on: | |
| # - Push to main branch | |
| # - Pull requests to main branch | |
| # | |
| # Jobs: | |
| # - lint: Runs ruff linter and formatter check | |
| # - typecheck: Runs ty (Red Knot) type checker | |
| # - test: Runs pytest with coverage on Python 3.12 + 3.13 (Ubuntu) and 3.13 | |
| # (Windows, where the platform-specific code actually lives) | |
| # - validate-examples: Validates all example workflow YAML files | |
| # - build: Verifies the package builds correctly | |
| # - frontend: Type-checks, tests (Vitest), and builds the web dashboard | |
| # - install-scripts: End-to-end exercise of install.ps1 / install.sh on | |
| # Windows + Ubuntu (opt-in pytest marker; ~90s) | |
| # - web-bg-smoke: Real `uv tool install` + `conductor run --web-bg` launcher | |
| # smoke test on Windows + Ubuntu (issue #447) | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| NODE_VERSION: "20" | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --group dev | |
| - name: Run ruff linter | |
| run: uv run ruff check src tests | |
| - name: Run ruff formatter check | |
| run: uv run ruff format --check src tests | |
| typecheck: | |
| name: Type Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --group dev | |
| - name: Run ty type checker | |
| run: uv run ty check src | |
| test: | |
| name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| needs: [lint, typecheck] | |
| strategy: | |
| matrix: | |
| os: [ubuntu-latest] | |
| python-version: ["3.12", "3.13"] | |
| # Windows carries a substantial amount of platform-specific code — | |
| # cli/pid.py is an entire OpenProcess/GetExitCodeProcess ctypes | |
| # implementation, cli/bg_runner.py handles process groups and job-object | |
| # breakaway — and none of it was executed anywhere. The existing tests | |
| # cover that logic on Linux by monkey-patching a `_kernel32` mock, which | |
| # is good design but means nothing validates the real ctypes signatures. | |
| # | |
| # Deliberately ONE Python version on Windows rather than a full 2x2: the | |
| # platform is the variable here, not the interpreter, and the | |
| # install-scripts job already provisions a Windows runner on every PR so | |
| # this is incremental cost rather than a new platform commitment. | |
| include: | |
| - os: windows-latest | |
| python-version: "3.13" | |
| fail-fast: false | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python ${{ matrix.python-version }} | |
| run: uv python install ${{ matrix.python-version }} | |
| - name: Install dependencies | |
| # Include the claude-agent-sdk extra so the provider's regression tests | |
| # (gated by `pytest.importorskip("claude_agent_sdk")`) actually run in | |
| # CI instead of silently skipping. | |
| run: uv sync --group dev --extra claude-agent-sdk | |
| - name: Remove bundled Copilot CLI binary | |
| # `shell: bash` so this runs identically on the Windows runner (Git Bash | |
| # is preinstalled). Without it the step fails on Windows, where a bare | |
| # `find` resolves to the unrelated DOS search tool in System32. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Copilot SDK <1.0.2 shipped per-platform wheels with a bundled CLI | |
| # binary that tries to authenticate with GitHub on startup. Remove it | |
| # so a test taking the real CLI path fails fast instead of hanging on | |
| # auth. | |
| # | |
| # The binary ships inside the package (copilot/bin/), not in the venv's | |
| # scripts directory — there is no console-script entry point — so one | |
| # pattern covers both platforms: the leading */ absorbs lib/pythonX.Y/ | |
| # or Lib/, and the trailing * absorbs the .exe suffix on Windows. | |
| # | |
| # Finding nothing is expected on the pinned SDK and is not an error: | |
| # the wheel is pure-Python from 1.0.2 on and fetches the CLI on first | |
| # use instead of bundling it. What stops that fetch from reinstating | |
| # the same hang is COPILOT_SKIP_CLI_DOWNLOAD in the test step's env, | |
| # so this step is now only for a venv restored from an older lock. | |
| removed=$(find .venv -path '*/copilot/bin/copilot*' -print -delete) | |
| if [ -n "$removed" ]; then | |
| echo "Removed bundled CLI: $removed" | |
| else | |
| echo "No bundled CLI present (expected for github-copilot-sdk >=1.0.2)." | |
| fi | |
| - name: Run tests with coverage | |
| # 20, not 10: the Fleet Manager added ~600 tests, most of them Textual | |
| # `App.run_test()` pilots, which took the Windows step from ~5m to | |
| # ~10m and tripped the old limit at 9m56s with every test passing. | |
| # Windows runs the suite at roughly twice the Linux wall time, so this | |
| # is ~2x headroom there and ~3x on Linux — still short enough to catch | |
| # a genuine hang rather than burning a full job timeout on one. | |
| timeout-minutes: 20 | |
| run: uv run pytest --cov=src/conductor --cov-report=xml --cov-report=term-missing -m "not real_api and not performance" | |
| env: | |
| # Fake API key for mock tests to prevent accidental real API calls. | |
| # Real API tests (marked with @pytest.mark.real_api) are excluded from CI | |
| # via the marker filter. Performance tests are also excluded as they | |
| # contain timing-sensitive assertions that are flaky on shared CI runners. | |
| # This ensures CI tests are fast, free, and don't leak credentials. | |
| ANTHROPIC_API_KEY: "sk-ant-test-fake-key-for-mocking" | |
| # The Copilot SDK fetches its CLI binary from GitHub Releases on | |
| # first use. A test that reaches the real CLI path should fail fast | |
| # rather than pull ~90MB inside a 10-minute job and then hang on auth. | |
| COPILOT_SKIP_CLI_DOWNLOAD: "1" | |
| - name: Upload coverage reports | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| # One upload per commit. Without the os guard a second runner would | |
| # upload a partial report for the same commit and skew the totals. | |
| if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest' | |
| with: | |
| file: ./coverage.xml | |
| fail_ci_if_error: false | |
| env: | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| validate-examples: | |
| name: Validate Example Workflows | |
| runs-on: ubuntu-latest | |
| needs: [lint] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync | |
| - name: Validate example workflows | |
| run: | | |
| for file in examples/*.yaml; do | |
| echo "Validating $file..." | |
| uv run conductor validate "$file" | |
| done | |
| frontend: | |
| name: Frontend (typecheck, test, build) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| cache-dependency-path: src/conductor/web/frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: src/conductor/web/frontend | |
| run: npm ci | |
| - name: Run Vitest | |
| working-directory: src/conductor/web/frontend | |
| run: npm run test | |
| - name: Type-check and build | |
| working-directory: src/conductor/web/frontend | |
| run: npm run build | |
| - name: Verify committed dashboard bundle is up to date | |
| # The built dashboard in src/conductor/web/static is committed to the | |
| # repo and shipped in the package (server.py serves it directly). If a | |
| # frontend source change lands without a matching `make build-frontend`, | |
| # the stale bundle ships silently. Fail the build when a fresh `npm run | |
| # build` produces any change under static/ that isn't committed. | |
| run: | | |
| if [ -n "$(git status --porcelain -- src/conductor/web/static)" ]; then | |
| echo "::error::src/conductor/web/static is out of date with the frontend source. Run 'make build-frontend' and commit the regenerated static/ assets." | |
| git status --porcelain -- src/conductor/web/static | |
| git diff -- src/conductor/web/static | head -n 200 | |
| exit 1 | |
| fi | |
| echo "Dashboard bundle is up to date." | |
| build: | |
| name: Build Package | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Build package | |
| run: uv build | |
| - name: Verify package contents | |
| run: | | |
| ls -la dist/ | |
| uv run python -m zipfile -l dist/*.whl | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 7 | |
| install-scripts: | |
| name: Install Scripts (${{ matrix.os }}) | |
| # The install-script suite is the only place install.ps1 / install.sh are | |
| # exercised end-to-end, so we run it on Windows (where the failure modes | |
| # actually live) and Ubuntu (parity sanity check). It builds two stamped | |
| # wheels and drives the scripts against an isolated UV_TOOL_DIR sandbox, | |
| # so the runner's environment is never modified. | |
| runs-on: ${{ matrix.os }} | |
| needs: [lint] | |
| strategy: | |
| matrix: | |
| os: [windows-latest, ubuntu-latest] | |
| fail-fast: false | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --group dev | |
| - name: Run install-script integration tests | |
| timeout-minutes: 10 | |
| run: uv run pytest -m install_scripts -v | |
| web-bg-smoke: | |
| name: Web BG Smoke (${{ matrix.os }}) | |
| # A launcher-level smoke test, not a workflow-execution test: it drives | |
| # the real `conductor run --web-bg` launch gate against a genuine `uv | |
| # tool install` on both platforms, which is what actually reproduces a | |
| # trampoline `sys.executable` (issue #444) and would have caught it -- | |
| # mocking the spawn seam, as the unit tests do, cannot. Runs on Windows | |
| # (where the trampoline actually happens) and Ubuntu (parity sanity | |
| # check). `examples/wait-smoke.yaml` needs no provider and caps itself | |
| # at `timeout_seconds: 15`, so the run completes well inside the launch | |
| # gate's own window and the assertion is on the *launcher*'s output, | |
| # not on workflow correctness. That cap is deliberately far above the | |
| # ~1s the fixture actually waits: a child that fails its *own* workflow | |
| # timeout exits non-zero, which the launch gate reports as a launch | |
| # failure, so a cap tight enough for a cold Windows runner to miss | |
| # would fail this job for a reason it does not test. | |
| runs-on: ${{ matrix.os }} | |
| needs: [lint] | |
| strategy: | |
| matrix: | |
| os: [windows-latest, ubuntu-latest] | |
| fail-fast: false | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | |
| with: | |
| version: "latest" | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install into an isolated tool sandbox | |
| # Mirrors the install-scripts job's UV_TOOL_DIR sandbox so this | |
| # never touches the runner's own environment. `uv tool install .` | |
| # on Windows is exactly what makes `sys.executable` a trampoline | |
| # (a wrapper `.exe` that re-execs the real interpreter) -- the | |
| # documented install path this job exists to cover. | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p "$RUNNER_TEMP/uv-tool-dir" "$RUNNER_TEMP/uv-tool-bin-dir" | |
| export UV_TOOL_DIR="$RUNNER_TEMP/uv-tool-dir" | |
| export UV_TOOL_BIN_DIR="$RUNNER_TEMP/uv-tool-bin-dir" | |
| echo "UV_TOOL_DIR=$UV_TOOL_DIR" >> "$GITHUB_ENV" | |
| echo "UV_TOOL_BIN_DIR=$UV_TOOL_BIN_DIR" >> "$GITHUB_ENV" | |
| echo "$RUNNER_TEMP/uv-tool-bin-dir" >> "$GITHUB_PATH" | |
| uv tool install . | |
| - name: Run conductor run --web-bg and assert launcher success | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| set +e | |
| output=$(conductor run examples/wait-smoke.yaml --web-bg 2>&1) | |
| status=$? | |
| set -e | |
| echo "$output" | |
| if [ "$status" -ne 0 ]; then | |
| echo "conductor run --web-bg exited with code $status" | |
| exit 1 | |
| fi | |
| if echo "$output" | grep -qi "already in use"; then | |
| echo "launch gate reported a false port conflict (issue #444/#447 regression)" | |
| exit 1 | |
| fi | |
| if echo "$output" | grep -qi "did not start within"; then | |
| echo "launch gate reported the dashboard never became reachable" | |
| exit 1 | |
| fi | |
| if echo "$output" | grep -qi "did not report a run record"; then | |
| echo "launch gate reported the child never wrote its run record" | |
| exit 1 | |
| fi |