Skip to content

Extended CI

Extended CI #2

Workflow file for this run

name: Extended CI
on:
merge_group:
workflow_dispatch:
inputs:
windows_guest:
description: Run the real OpenSandbox Windows guest lifecycle test
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
e2e-windows:
name: E2E (none runtime, Windows)
runs-on: self-hosted
timeout-minutes: 20
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "1.25.x"
cache: true
- name: Run e2e tests (none runtime, quick mode)
shell: bash
run: go test -tags e2e -timeout 1200s -count=1 -v ./e2e
env:
SKILL_UP_E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts
- name: Upload e2e workspace artifacts
if: always() && hashFiles('e2e-artifacts/**') != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-windows-workspaces
path: e2e-artifacts/
if-no-files-found: ignore
retention-days: 14
e2e:
name: E2E (none runtime)
runs-on: self-hosted
timeout-minutes: 35
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check secret availability
id: secrets
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
run: |
if [ -z "$DASHSCOPE_API_KEY" ]; then
echo "::notice::Skipping e2e: DASHSCOPE_API_KEY not provisioned."
echo "available=false" >> "$GITHUB_OUTPUT"
else
echo "available=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
if: steps.secrets.outputs.available == 'true'
with:
go-version: "1.25.x"
cache: true
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
if: steps.secrets.outputs.available == 'true'
with:
node-version: "22"
- name: Install claude-code CLI
if: steps.secrets.outputs.available == 'true'
run: |
npm install -g --include=optional @anthropic-ai/claude-code
claude --version
- name: Install codex CLI
if: steps.secrets.outputs.available == 'true'
run: |
npm install -g --include=optional '@openai/codex@0.80.0'
codex --version
- name: Install qwen-code CLI
if: steps.secrets.outputs.available == 'true'
run: |
npm install -g @qwen-code/qwen-code
qwen --version
- name: Install qodercli (pinned v1.0.14)
if: steps.secrets.outputs.available == 'true'
env:
QODER_VERSION: "1.0.14"
QODER_SHA256: "4d83ec3d3a0948b73012cfdd6e2757be0a5da5c89a288425a5607fd42be1de7b"
run: |
TARBALL=$(mktemp)
curl -fsSL "https://qoder-ide.oss-accelerate.aliyuncs.com/qodercli/releases/${QODER_VERSION}/qodercli-linux-x64.tar.gz" -o "$TARBALL"
echo "$QODER_SHA256 $TARBALL" | sha256sum -c -
mkdir -p "$HOME/.qoder/bin"
tar -xzf "$TARBALL" -C "$HOME/.qoder/bin"
rm -f "$TARBALL"
echo "$HOME/.qoder/bin" >> "$GITHUB_PATH"
- name: Verify qodercli on PATH
if: steps.secrets.outputs.available == 'true'
run: |
which qodercli
qodercli --version || true
- name: Run e2e tests (none runtime, full mode)
if: steps.secrets.outputs.available == 'true'
run: go test -tags e2e -timeout 1800s -count=1 -v ./e2e
env:
SKILL_UP_FULL_E2E: "1"
SKILL_UP_E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-artifacts
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
DASHSCOPE_BASE_URL: https://dashscope.aliyuncs.com/apps/anthropic
ANTHROPIC_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
ANTHROPIC_BASE_URL: https://dashscope.aliyuncs.com/apps/anthropic
ANTHROPIC_MODEL: qwen3.6-plus
OPENAI_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
OPENAI_BASE_URL: https://dashscope.aliyuncs.com/compatible-mode/v1
OPENAI_MODEL: qwen3.6-plus
DASHSCOPE_MODEL: qwen3.6-plus
QODER_PERSONAL_ACCESS_TOKEN: ${{ secrets.QODER_ACCESS_TOKEN }}
- name: Upload e2e workspace artifacts
if: always() && steps.secrets.outputs.available == 'true' && hashFiles('e2e-artifacts/**') != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-workspaces
path: e2e-artifacts/
if-no-files-found: ignore
retention-days: 14
e2e-opensandbox:
name: E2E (opensandbox runtime)
runs-on: self-hosted
timeout-minutes: 35
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check secret availability
id: secrets
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
run: |
if [ -z "$DASHSCOPE_API_KEY" ]; then
echo "::notice::Skipping opensandbox e2e: DASHSCOPE_API_KEY not provisioned."
echo "available=false" >> "$GITHUB_OUTPUT"
else
echo "available=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
if: steps.secrets.outputs.available == 'true'
with:
go-version: "1.25.x"
cache: true
- name: Install uv
if: steps.secrets.outputs.available == 'true'
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install OpenSandbox server
if: steps.secrets.outputs.available == 'true'
run: |
uv tool install 'opensandbox-server==0.1.13'
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Start OpenSandbox server
if: steps.secrets.outputs.available == 'true'
env:
OPENSANDBOX_IMAGE: node:22
OPENSANDBOX_EXECD_IMAGE: opensandbox/execd:v1.0.16
run: |
docker version
api_key="ci-$(openssl rand -hex 16)"
config="$RUNNER_TEMP/sandbox.toml"
cat > "$config" <<EOF
[server]
host = "127.0.0.1"
port = 8081
api_key = "$api_key"
[runtime]
type = "docker"
execd_image = "$OPENSANDBOX_EXECD_IMAGE"
[docker]
network_mode = "host"
EOF
log="$RUNNER_TEMP/opensandbox-server.log"
nohup opensandbox-server --config "$config" > "$log" 2>&1 &
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:8080/health 2>/dev/null | grep -q healthy; then
echo "OpenSandbox server is healthy"
break
fi
if [ "$i" -eq 60 ]; then
echo "::error::OpenSandbox server did not become healthy within 120s"
cat "$log"
exit 1
fi
sleep 2
done
{
echo "OPENSANDBOX_API_KEY=$api_key"
echo "OPENSANDBOX_BASE_URL=http://127.0.0.1:8080"
echo "OPENSANDBOX_IMAGE=$OPENSANDBOX_IMAGE"
} >> "$GITHUB_ENV"
- name: Run e2e tests (opensandbox runtime)
if: steps.secrets.outputs.available == 'true'
run: go test -tags e2e -timeout 1800s -count=1 -v -run TestAgent_Codex_OpenSandboxRuntime ./e2e
env:
OPENAI_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
OPENAI_BASE_URL: https://dashscope.aliyuncs.com/compatible-mode/v1
OPENAI_MODEL: qwen3.6-plus
SKILL_UP_E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-opensandbox-artifacts
- name: Upload OpenSandbox server log
if: always() && steps.secrets.outputs.available == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: opensandbox-server-log
path: ${{ runner.temp }}/opensandbox-server.log
if-no-files-found: ignore
retention-days: 14
- name: Upload opensandbox e2e workspace artifacts
if: always() && steps.secrets.outputs.available == 'true' && hashFiles('e2e-opensandbox-artifacts/**') != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-opensandbox-workspaces
path: e2e-opensandbox-artifacts/
if-no-files-found: ignore
retention-days: 14
e2e-opensandbox-windows:
name: E2E (OpenSandbox Windows guest)
if: github.event_name == 'workflow_dispatch' && inputs.windows_guest
runs-on: self-hosted
timeout-minutes: 35
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check Windows guest prerequisites
id: prerequisites
run: |
available=true
for device in /dev/kvm /dev/net/tun; do
if [ ! -r "$device" ] || [ ! -w "$device" ]; then
echo "::notice::Skipping Windows guest e2e: $device is unavailable or not readable/writable."
available=false
fi
done
if ! docker info >/dev/null 2>&1; then
echo "::notice::Skipping Windows guest e2e: Docker is unavailable."
available=false
fi
echo "available=$available" >> "$GITHUB_OUTPUT"
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
if: steps.prerequisites.outputs.available == 'true'
with:
go-version: "1.25.x"
cache: true
- name: Install uv
if: steps.prerequisites.outputs.available == 'true'
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Install OpenSandbox server with Windows profile
if: steps.prerequisites.outputs.available == 'true'
run: |
uv tool install 'opensandbox-server==0.2.1'
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Start OpenSandbox server
if: steps.prerequisites.outputs.available == 'true'
run: |
api_key="ci-$(openssl rand -hex 16)"
config="$RUNNER_TEMP/sandbox-windows.toml"
cat > "$config" <<EOF
[server]
host = "127.0.0.1"
port = 8080
api_key = "$api_key"
[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.0.21"
[docker]
network_mode = "host"
EOF
log="$RUNNER_TEMP/opensandbox-windows-server.log"
nohup opensandbox-server --config "$config" > "$log" 2>&1 &
echo $! > "$RUNNER_TEMP/opensandbox-windows-server.pid"
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:8081/health 2>/dev/null | grep -q healthy; then
break
fi
if [ "$i" -eq 60 ]; then
echo "::error::OpenSandbox server did not become healthy within 120s"
cat "$log"
exit 1
fi
sleep 2
done
{
echo "OPENSANDBOX_API_KEY=$api_key"
echo "OPENSANDBOX_BASE_URL=http://127.0.0.1:8081"
echo "OPENSANDBOX_WINDOWS_IMAGE=dockurr/windows:latest"
} >> "$GITHUB_ENV"
- name: Run real Windows guest lifecycle test
if: steps.prerequisites.outputs.available == 'true'
run: go test -tags e2e -timeout 1800s -count=1 -v -run TestCustomEngine_OpenSandboxWindowsGuest ./e2e
env:
SKILL_UP_WINDOWS_GUEST_E2E: "1"
SKILL_UP_E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-opensandbox-windows-artifacts
- name: Upload Windows guest diagnostics
if: always() && steps.prerequisites.outputs.available == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: opensandbox-windows-guest
path: |
${{ runner.temp }}/opensandbox-windows-server.log
e2e-opensandbox-windows-artifacts/
if-no-files-found: ignore
retention-days: 14
- name: Stop OpenSandbox server
if: always() && steps.prerequisites.outputs.available == 'true'
run: |
if [ -f "$RUNNER_TEMP/opensandbox-windows-server.pid" ]; then
kill "$(cat "$RUNNER_TEMP/opensandbox-windows-server.pid")" 2>/dev/null || true
fi
e2e-docker:
name: E2E (docker runtime)
runs-on: self-hosted
timeout-minutes: 20
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "1.25.x"
cache: true
- name: Verify docker is available
run: |
docker version
docker info
- name: Pre-pull base image
run: docker pull alpine:3.20
- name: Run docker integration tests
run: go test -tags docker_integration -timeout 600s -count=1 -v ./internal/runtime/
e2e-docker-full:
name: E2E (docker runtime, full LLM)
runs-on: self-hosted
timeout-minutes: 25
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check secret availability
id: secrets
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
run: |
if [ -z "$DASHSCOPE_API_KEY" ]; then
echo "::notice::Skipping docker full e2e: DASHSCOPE_API_KEY not provisioned."
echo "available=false" >> "$GITHUB_OUTPUT"
else
echo "available=true" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
if: steps.secrets.outputs.available == 'true'
with:
go-version: "1.25.x"
cache: true
- name: Pre-pull Docker image
if: steps.secrets.outputs.available == 'true'
run: docker pull node:22
- name: Run docker full e2e
if: steps.secrets.outputs.available == 'true'
run: go test -tags e2e -timeout 1800s -count=1 -v -run TestAgent_ClaudeCode_DockerRuntime ./e2e
env:
SKILL_UP_FULL_E2E: "1"
SKILL_UP_E2E_ARTIFACT_DIR: ${{ github.workspace }}/e2e-docker-full-artifacts
ANTHROPIC_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
ANTHROPIC_BASE_URL: https://dashscope.aliyuncs.com/apps/anthropic
ANTHROPIC_MODEL: qwen3.6-plus
- name: Upload docker full e2e artifacts
if: always() && steps.secrets.outputs.available == 'true' && hashFiles('e2e-docker-full-artifacts/**') != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-docker-full-workspaces
path: e2e-docker-full-artifacts/
if-no-files-found: ignore
retention-days: 14
release-dryrun:
name: GoReleaser Check
runs-on: self-hosted
timeout-minutes: 15
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "1.25.x"
cache: true
- name: GoReleaser check
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
with:
distribution: goreleaser
version: "~> v2"
args: check
- name: GoReleaser snapshot build
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
with:
distribution: goreleaser
version: "~> v2"
args: release --snapshot --clean --skip=publish