ci: run e2e quick-mode suite on every PR #31
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
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - 'docs/**' | |
| - '*.md' | |
| - '.github/workflows/docs.yml' | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - 'docs/**' | |
| - '*.md' | |
| - '.github/workflows/docs.yml' | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| name: Build & Test | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| go-version: ["1.25.x"] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: ${{ matrix.go-version }} | |
| cache: true | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Build | |
| run: go build ./... | |
| - name: Test (with race detector and coverage) | |
| run: go test -race -timeout 120s -covermode=atomic -coverprofile=coverage.out ./... | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./coverage.out | |
| flags: unittests | |
| fail_ci_if_error: false | |
| env: | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| e2e: | |
| name: E2E (none runtime) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| # Fork PRs run without access to repository secrets. Detect that early | |
| # and skip the rest of the job so we don't burn CI minutes installing | |
| # CLIs only to have every LLM-dependent test hit a 401. | |
| - 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 (likely a fork PR)." | |
| echo "available=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "available=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: actions/setup-go@v6 | |
| if: steps.secrets.outputs.available == 'true' | |
| with: | |
| go-version: "1.25.x" | |
| cache: true | |
| - uses: actions/setup-node@v5 | |
| 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: | | |
| # Pin to the version declared in internal/agent/codex.go (codexDefaultVersion). | |
| npm install -g --include=optional '@openai/codex@0.80.0' | |
| codex --version | |
| - name: Install qodercli | |
| if: steps.secrets.outputs.available == 'true' | |
| run: | | |
| curl -fsSL https://qoder.com/install | bash | |
| # qoder.com installer drops the binary under ~/.qoder/bin by default; | |
| # surface it on PATH for the rest of the job. | |
| 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' | |
| # SKILL_UP_FULL_E2E unlocks tests that drive a real model. DashScope's | |
| # Anthropic- and OpenAI-compatible endpoints back claude-code and codex | |
| # respectively; QODER_PERSONAL_ACCESS_TOKEN authenticates qodercli. | |
| run: go test -tags e2e -timeout 1800s -count=1 -v ./e2e | |
| env: | |
| SKILL_UP_FULL_E2E: "1" | |
| DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} | |
| # claude_code + dashscope provider → routed through the Anthropic-compat endpoint. | |
| DASHSCOPE_BASE_URL: https://dashscope.aliyuncs.com/apps/anthropic | |
| # Pin every anthropic-provider hop to qwen3.6-plus so no e2e path | |
| # accidentally calls real Claude, regardless of which model the | |
| # eval.yaml (or any hard-coded fallback elsewhere in the codebase) | |
| # would otherwise pick. | |
| ANTHROPIC_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} | |
| ANTHROPIC_BASE_URL: https://dashscope.aliyuncs.com/apps/anthropic | |
| ANTHROPIC_MODEL: qwen3.6-plus | |
| # Same idea for openai-provider hops (codex test pins "gpt-5.4" in | |
| # its eval.yaml; OPENAI_MODEL overrides that to 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 }} | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version: "1.25.x" | |
| cache: true | |
| - name: Check formatting | |
| run: | | |
| if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then | |
| echo "Following files are not formatted:" | |
| gofmt -l . | |
| exit 1 | |
| fi | |
| - name: golangci-lint | |
| uses: golangci/golangci-lint-action@v8 | |
| with: | |
| version: v2.11.4 | |
| - name: Install revive | |
| run: go install github.com/mgechev/revive@v1.10.0 | |
| - name: revive | |
| run: revive -config revive.toml ./... | |
| release-dryrun: | |
| name: GoReleaser Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1.25.x" | |
| cache: true | |
| - name: GoReleaser check | |
| uses: goreleaser/goreleaser-action@v7 | |
| with: | |
| distribution: goreleaser | |
| version: "~> v2" | |
| args: check | |
| - name: GoReleaser snapshot build | |
| uses: goreleaser/goreleaser-action@v7 | |
| with: | |
| distribution: goreleaser | |
| version: "~> v2" | |
| args: release --snapshot --clean --skip=publish |