diff --git a/.github/workflows/README-RELEASE.md b/.github/workflows/README-RELEASE.md index 9a478b6c33..b4fe82e709 100644 --- a/.github/workflows/README-RELEASE.md +++ b/.github/workflows/README-RELEASE.md @@ -8,7 +8,7 @@ The release process has been automated with three GitHub Actions workflows: 1. **prepare-release.yml** - Prepares a release PR with version updates 2. **pypi-release.yml** - Automatically publishes packages to PyPI when a release is created -3. **release-binaries.yml** - Builds and smoke-tests multi-arch agent-server binaries +3. **release-binaries.yml** - Builds and smoke-tests the linux x86_64 agent-server binary on releases and main pushes; release runs also attach binaries to the release ## How to Create a New Release @@ -63,17 +63,17 @@ You can monitor the progress in the [Actions tab](https://github.com/OpenHands/s In parallel with the PyPI workflow, **release-binaries.yml** also fires on `release: published`. It also runs on every push to `main` as ongoing smoke coverage. It: -- ✅ Builds the agent-server PyInstaller binary on a 5-runner matrix - (linux x86_64/arm64, macOS x86_64/arm64, windows x86_64) and smoke-tests each +- ✅ Builds the agent-server PyInstaller binary on `ubuntu-24.04` (linux x86_64 + only; macOS, Windows and arm64 builds were removed) and smoke-tests it - ✅ Exports and validates the deterministic public Agent Server contract as `openapi.json`, with `info.version` matching the release version - ✅ Generates a combined `SHA256SUMS` and attaches the binaries and `openapi.json` to the GitHub release on release/manual runs -- ✅ Verifies that the multi-arch Docker manifest +- ✅ Verifies that the Docker manifest `ghcr.io/openhands/agent-server:-` published by - `server.yml` covers both `linux/amd64` and `linux/arm64` for every variant + `server.yml` covers `linux/amd64` for every variant (`python`, `java`, `golang`) -- ✅ Pulls each variant on each architecture with `--platform=linux/`, +- ✅ Pulls each variant with `--platform=linux/amd64`, boots the container, and asserts `/health` responds On `push` events, `` is the 7-character commit SHA and binaries plus @@ -85,18 +85,17 @@ uploaded to the GitHub release. | Stage | Runtime (typical) | Runners | |---|---|---| -| Binary builds (5-way matrix, parallel) | ~10–15 min on Linux, ~12–18 min on macOS | `ubuntu-24.04`, `ubuntu-24.04-arm`, `macos-15-intel`, `macos-14`, `windows-2022` | +| Binary build (single linux x86_64 leg) | ~10–15 min | `ubuntu-24.04` | | `publish-binaries` (download + checksum + upload) | ~1–2 min | `ubuntu-24.04` | -| `docker-smoke-test` (6-way matrix, parallel) | Up to 45 min (mostly polling for the docker images) | `ubuntu-24.04` for amd64, `ubuntu-24.04-arm` for arm64 | +| `docker-smoke-test` (3-way matrix, parallel) | Up to 45 min (mostly polling for the docker images) | `ubuntu-24.04` (amd64) | #### QEMU / buildx requirements -The smoke test does **not** require QEMU: each (variant, arch) job runs on a -runner whose architecture matches `--platform=linux/`, so containers run -natively. We do still set up Docker Buildx so we can call -`docker buildx imagetools inspect` on the multi-arch manifest list. +The smoke test does **not** require QEMU: every job runs on an `ubuntu-24.04` +(x86_64) runner and pulls `--platform=linux/amd64`, so containers run natively. We do still set up Docker Buildx so we can call +`docker buildx imagetools inspect` on the manifest. -The wait window for the multi-arch manifest is 45 min — long enough to absorb +The wait window for the manifest is 45 min — long enough to absorb the full `server.yml` matrix runtime (~25–30 min for `build-and-push-image` + `merge-manifests`) when this workflow races the corresponding `server.yml` run for a release tag or main-branch push. @@ -134,11 +133,15 @@ If you need to manually trigger the PyPI release workflow: 4. Select the branch/tag you want to publish from 5. Click **"Run workflow"** +## CI scope and required checks + +CI targets Ubuntu linux/amd64 only (sdk#82). Removed checks: `windows-tests` (`tests.yml`); `build-binary-and-test (macos-latest)` and `(windows-latest)`, `Build & Push (-arm64)` (`server.yml`); `Build (linux-arm64)`, `Build (macos-x86_64)`, `Build (macos-arm64)`, `Build (windows-x86_64)`, `Docker (-arm64)` (`release-binaries.yml`). Remaining check names are unchanged. Main branch protection could not be read with the available token (403; rulesets API returns `[]`), so an operator must confirm in the GitHub UI that no removed check is a required status check and drop it if so; this was not changed from the repository. + ## Workflow Files - `.github/workflows/prepare-release.yml` - Automated release preparation - `.github/workflows/pypi-release.yml` - PyPI package publication -- `.github/workflows/release-binaries.yml` - Multi-arch binary publishing and +- `.github/workflows/release-binaries.yml` - Linux x86_64 binary publishing and docker manifest smoke test on releases and main pushes ## Troubleshooting @@ -167,7 +170,7 @@ If `release-binaries.yml` fails: - **Binary build failure**: re-run the failed matrix job; PyInstaller flakes are rare but possible. If it persists, the issue is likely in `agent-server.spec`. - **`docker-smoke-test` timed out waiting for the manifest**: `server.yml` did - not publish multi-arch images for the matching release tag or commit SHA. + not publish images for the matching release tag or commit SHA. Check that workflow's corresponding run and re-trigger if needed. - **`/health` never responded**: open the failing job; the cleanup trap dumps the last 100 lines of `docker logs` for the container. diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index e114bef202..323e772446 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -2,14 +2,16 @@ name: Publish agent-server release artifacts # On release published or push to main: -# 1. Build the agent-server PyInstaller binary on Linux + macOS for both -# x86_64 and arm64, smoke-test it, and upload workflow artifacts. -# 2. On release events/manual runs, attach those binaries plus a combined +# 1. Build the agent-server PyInstaller binary on Linux x86_64, smoke-test +# it, and upload workflow artifacts. +# 2. On release events/manual runs, attach that binary plus a combined # SHA256SUMS file to the GitHub release. -# 3. Smoke-test the multi-arch Docker images pushed by `server.yml`, -# verifying that every published variant has a manifest covering both -# linux/amd64 and linux/arm64 and that the container actually boots -# and answers /health on each architecture. +# 3. Smoke-test the linux/amd64 Docker images pushed by `server.yml`, +# verifying that every published variant has a manifest covering +# linux/amd64 and that the container actually boots and answers /health. +# +# CI scope: Ubuntu linux/amd64 only. macOS, Windows and linux/arm64 legs were +# removed (sdk#82); the single-entry matrices keep the remaining check names. on: push: @@ -81,20 +83,6 @@ jobs: - runner: ubuntu-24.04 os_label: linux arch: x86_64 - - runner: ubuntu-24.04-arm - os_label: linux - arch: arm64 - # macos-13 (Intel) was retired by GitHub on 2025-12-04; - # macos-15-intel is the last x86_64 macOS image (until ~2027). - - runner: macos-15-intel - os_label: macos - arch: x86_64 - - runner: macos-14 - os_label: macos - arch: arm64 - - runner: windows-2022 - os_label: windows - arch: x86_64 steps: - name: Checkout uses: actions/checkout@v7 @@ -108,25 +96,15 @@ jobs: - name: Install dependencies run: uv sync --dev - - name: Build binary (Unix) - if: runner.os != 'Windows' + - name: Build binary run: make build-server - - name: Build binary (Windows) - if: runner.os == 'Windows' - shell: bash - run: uv run pyinstaller openhands-agent-server/openhands/agent_server/agent-server.spec - - name: Smoke-test binary shell: bash run: | set -euo pipefail - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - BIN=./dist/openhands-agent-server.exe - else - BIN=./dist/openhands-agent-server - fi + BIN=./dist/openhands-agent-server "$BIN" --help @@ -173,11 +151,7 @@ jobs: run: | set -euo pipefail mkdir -p release-assets - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - cp dist/openhands-agent-server.exe "release-assets/${ASSET}.exe" - else - cp dist/openhands-agent-server "release-assets/${ASSET}" - fi + cp dist/openhands-agent-server "release-assets/${ASSET}" ls -la release-assets/ - name: Upload binary as workflow artifact @@ -297,21 +271,12 @@ jobs: - variant: python arch: amd64 runner: ubuntu-24.04 - - variant: python - arch: arm64 - runner: ubuntu-24.04-arm - variant: java arch: amd64 runner: ubuntu-24.04 - - variant: java - arch: arm64 - runner: ubuntu-24.04-arm - variant: golang arch: amd64 runner: ubuntu-24.04 - - variant: golang - arch: arm64 - runner: ubuntu-24.04-arm env: IMAGE: ${{ format('ghcr.io/{0}/agent-server', github.repository_owner) }} IMAGE_TAG: ${{ needs.resolve-tag.outputs.image_tag }} @@ -331,7 +296,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Wait for multi-arch manifest + - name: Wait for manifest shell: bash run: | set -euo pipefail @@ -347,7 +312,7 @@ jobs: done echo "✓ Manifest available: $TAG_FQN" - - name: Verify manifest covers linux/amd64 + linux/arm64 + - name: Verify manifest covers linux/amd64 shell: bash run: | set -euo pipefail @@ -357,13 +322,11 @@ jobs: | sort -u) echo "Platforms in $TAG_FQN:" echo "$PLATFORMS" - for required in linux/amd64 linux/arm64; do - if ! echo "$PLATFORMS" | grep -qx "$required"; then - echo "ERROR: $required missing from $TAG_FQN manifest" - exit 1 - fi - done - echo "✓ Both linux/amd64 and linux/arm64 are present" + if ! echo "$PLATFORMS" | grep -qx linux/amd64; then + echo "ERROR: linux/amd64 missing from $TAG_FQN manifest" + exit 1 + fi + echo "✓ linux/amd64 is present" - name: Pull and run on linux/${{ matrix.arch }} shell: bash diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index 2421afc2c5..7cc0674965 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -21,7 +21,7 @@ on: platforms: description: Target platforms type: string - default: linux/amd64,linux/arm64 + default: linux/amd64 install_acp_providers: description: Comma-separated ACP provider keys to bake into the image type: string @@ -35,13 +35,17 @@ permissions: contents: read packages: write +# CI scope: Ubuntu linux/amd64 only. Windows, macOS and linux/arm64 jobs were +# removed (sdk#82); the single-entry matrices below are kept so the existing +# check names (e.g. `build-binary-and-test (ubuntu-latest)`) do not change. + jobs: build-binary-and-test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest] steps: - uses: actions/checkout@v7 @@ -53,26 +57,15 @@ jobs: - name: Install dependencies run: uv sync --dev - - name: Build binary (Unix) - if: runner.os != 'Windows' + - name: Build binary run: make build-server - # Windows runners have no `make`; invoke PyInstaller directly. - - name: Build binary (Windows) - if: runner.os == 'Windows' - shell: bash - run: uv run pyinstaller openhands-agent-server/openhands/agent_server/agent-server.spec - - name: Test binary shell: bash run: | set -euo pipefail - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - BIN=./dist/openhands-agent-server.exe - else - BIN=./dist/openhands-agent-server - fi + BIN=./dist/openhands-agent-server "$BIN" --help @@ -115,11 +108,7 @@ jobs: run: | set -euo pipefail - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - BIN=./dist/openhands-agent-server.exe - else - BIN=./dist/openhands-agent-server - fi + BIN=./dist/openhands-agent-server wait_for_log() { local log_file=$1 @@ -143,10 +132,6 @@ jobs: # Create a temporary directory with an external tool module TOOL_DIR=$(mktemp -d) - EXTRA_TOOL_DIR=$TOOL_DIR - if [[ "${RUNNER_OS:-}" == "Windows" ]]; then - EXTRA_TOOL_DIR=$(cygpath -w "$TOOL_DIR") - fi cat > "$TOOL_DIR/ci_test_tool.py" << 'TOOL_EOF' """CI smoke-test tool: NOT bundled in the binary. @@ -176,7 +161,7 @@ jobs: rm -f neg_test.log echo "=== Positive test: import WITH OH_EXTRA_PYTHON_PATH ===" - OH_EXTRA_PYTHON_PATH="$EXTRA_TOOL_DIR" \ + OH_EXTRA_PYTHON_PATH="$TOOL_DIR" \ "$BIN" --import-modules ci_test_tool --port 8004 \ > pos_test.log 2>&1 & POS_PID=$! @@ -204,7 +189,7 @@ jobs: stop_process "$POS_PID" echo "=== Positive test: import WITH --extra-python-path CLI flag ===" - "$BIN" --extra-python-path "$EXTRA_TOOL_DIR" \ + "$BIN" --extra-python-path "$TOOL_DIR" \ --import-modules ci_test_tool --port 8005 \ > cli_test.log 2>&1 & CLI_PID=$! @@ -278,48 +263,26 @@ jobs: strategy: fail-fast: false matrix: - # Explicit matrix: 3 variants × 2 architectures = 6 jobs - # Each job specifies exactly what it builds and where it runs + # Explicit matrix: 3 variants x linux/amd64 = 3 jobs include: - # Python variant - variant: python arch: amd64 base_image: nikolaik/python-nodejs:python3.13-nodejs22-slim runner: ubuntu-24.04 platform: linux/amd64 - - variant: python - arch: arm64 - base_image: nikolaik/python-nodejs:python3.13-nodejs22-slim - runner: ubuntu-24.04-arm - platform: linux/arm64 - - # Java variant - variant: java arch: amd64 base_image: eclipse-temurin:17-jdk runner: ubuntu-24.04 platform: linux/amd64 - - variant: java - arch: arm64 - base_image: eclipse-temurin:17-jdk - runner: ubuntu-24.04-arm - platform: linux/arm64 - - # Golang variant - variant: golang arch: amd64 base_image: golang:1.21-bookworm runner: ubuntu-24.04 platform: linux/amd64 - - variant: golang - arch: arm64 - base_image: golang:1.21-bookworm - runner: ubuntu-24.04-arm - platform: linux/arm64 - runs-on: ${{ matrix.runner }} env: @@ -529,7 +492,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Create and push multi-arch manifest for ${{ matrix.variant }} + - name: Create and push amd64 manifest tags for ${{ matrix.variant }} id: create_manifest env: GIT_REF: ${{ github.ref }} @@ -544,14 +507,13 @@ jobs: local manifest_tag=$1 local source_tag=${2:-$1} - echo "Creating multi-arch manifest: ${IMAGE}:${manifest_tag}" + echo "Creating arch-less manifest tag: ${IMAGE}:${manifest_tag}" docker buildx imagetools create -t ${IMAGE}:${manifest_tag} \ - ${IMAGE}:${source_tag}-amd64 \ - ${IMAGE}:${source_tag}-arm64 + ${IMAGE}:${source_tag}-amd64 - echo "Inspecting multi-arch manifest:" + echo "Inspecting manifest:" docker buildx imagetools inspect ${IMAGE}:${manifest_tag} - echo "✓ Multi-arch manifest created: ${IMAGE}:${manifest_tag}" + echo "✓ Manifest created: ${IMAGE}:${manifest_tag}" } IFS=',' read -ra AMD64_TAGS <<< "$AMD64_TAGS_CSV" @@ -815,7 +777,7 @@ jobs: echo "DEBUG: Processing build JSON: $VARIANT_JSON" CUSTOM_TAGS=$(echo "$VARIANT_JSON" | jq -r '.custom_tags') BASE_IMAGE=$(echo "$VARIANT_JSON" | jq -r '.base_image') - ARCHS=$(echo "$VARIANT_JSON" | jq -r '.architectures // "amd64, arm64"') + ARCHS=$(echo "$VARIANT_JSON" | jq -r '.architectures // "amd64"') echo "DEBUG: Adding variant $CUSTOM_TAGS with base image $BASE_IMAGE (archs: $ARCHS)" # Add to variants table with architecture info @@ -840,9 +802,9 @@ jobs: |---|---|---|---| ${VARIANTS_TABLE} - **Pull (multi-arch manifest)** + **Pull (linux/amd64)** \`\`\`bash - # Each variant is a multi-arch manifest supporting both amd64 and arm64 + # Each variant tag is a linux/amd64 image docker pull ${IMAGE}:${SHORT_SHA}-python \`\`\` @@ -859,10 +821,9 @@ jobs: ${ALL_TAGS} \`\`\` - **About Multi-Architecture Support** - - Each variant tag (e.g., \`${SHORT_SHA}-python\`) is a **multi-arch manifest** supporting both **amd64** and **arm64** - - Docker automatically pulls the correct architecture for your platform - - Individual architecture tags (e.g., \`${SHORT_SHA}-python-amd64\`) are also available if needed + **About Architecture Support** + - Each variant tag (e.g., \`${SHORT_SHA}-python\`) is a **linux/amd64** image; arm64 images are not published + - The architecture-suffixed tag (e.g., \`${SHORT_SHA}-python-amd64\`) is also available EOF diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6c4e78180d..9bae60a8c4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -194,81 +194,6 @@ jobs: path: coverage-tools.dat if-no-files-found: warn - windows-tests: - runs-on: windows-latest - timeout-minutes: 30 - env: - PYTHONUTF8: '1' - steps: - - name: Checkout - uses: actions/checkout@v7 - with: {fetch-depth: 0} - - - name: Detect Windows-relevant changes - id: changed - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47 - with: - files: | - openhands-tools/** - tests/tools/** - pyproject.toml - uv.lock - .github/workflows/tests.yml - - - name: Install uv - if: steps.changed.outputs.any_changed == 'true' - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 - with: - enable-cache: true - python-version: '3.13' - - - name: Install deps - if: steps.changed.outputs.any_changed == 'true' - run: uv sync --frozen --group dev - - - name: Install Chromium - if: steps.changed.outputs.any_changed == 'true' - run: uvx playwright install chromium - - - name: Run Windows test suite - if: steps.changed.outputs.any_changed == 'true' - run: | - if (Test-Path .coverage) { - Remove-Item .coverage -Force - } - $env:CI = 'true' - # Keep the initial Windows pass non-blocking on coverage while - # OS-specific gaps tracked in #2989 are still open. - # Browser/file-editor e2e and terminal shell assumptions remain - # tracked in #2986 and #2988. - uv run python -m pytest -vvs ` - --cov=openhands-tools ` - --cov-report=term-missing ` - --cov-fail-under=0 ` - --cov-config=pyproject.toml ` - tests/tools ` - --ignore=tests/tools/browser_use/test_browser_executor_e2e.py ` - --ignore=tests/tools/file_editor/test_memory_usage.py ` - --ignore=tests/tools/terminal/test_conversation_cleanup.py ` - --ignore=tests/tools/terminal/test_session_factory.py ` - --ignore=tests/tools/terminal/test_shell_path_configuration.py ` - --ignore=tests/tools/terminal/test_shutdown_handling.py ` - --ignore=tests/tools/terminal/test_terminal_session.py ` - --ignore=tests/tools/terminal/test_terminal_tool_auto_detection.py - if (Test-Path .coverage) { - Move-Item .coverage coverage-windows.dat - Write-Host 'Windows coverage file prepared for upload' - } - - - name: Upload Windows coverage - if: steps.changed.outputs.any_changed == 'true' && always() - uses: actions/upload-artifact@v7 - with: - name: coverage-windows - path: coverage-windows.dat - if-no-files-found: warn - - agent-server-tests: runs-on: ubuntu-latest steps: