Skip to content

feat: add deterministic type: mcp workflow step #1162

feat: add deterministic type: mcp workflow step

feat: add deterministic type: mcp workflow step #1162

Workflow file for this run

# 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