Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ updates:
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 3
open-pull-requests-limit: 1
groups:
github-actions:
patterns:
- "*"
labels:
- "dependencies"
- "ci"
Expand Down
500 changes: 19 additions & 481 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

337 changes: 337 additions & 0 deletions .github/workflows/extended-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
name: Extended CI

on:
merge_group:
workflow_dispatch:

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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0

- 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 = 8080
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-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@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
with:
distribution: goreleaser
version: "~> v2"
args: check

- name: GoReleaser snapshot build
uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7
with:
distribution: goreleaser
version: "~> v2"
args: release --snapshot --clean --skip=publish
Loading
Loading