Skip to content

Upgrade GitHub Actions for Node 24 compatibility #38072

Upgrade GitHub Actions for Node 24 compatibility

Upgrade GitHub Actions for Node 24 compatibility #38072

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
paths:
- '**.go'
- 'pkg/workflow/**'
- 'actions/**'
- '.github/workflows/ci.yml'
- '.github/workflows/**/*.md'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-test
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Display Go environment
run: |
echo "Go environment:"
go env | grep -E "GOPROXY|GOSUMDB|GOMODCACHE|GOPRIVATE"
echo ""
echo "Module cache location: $(go env GOMODCACHE)"
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
# Use -x for verbose output to see what's being downloaded
if go mod download -x; then
echo "✅ Successfully downloaded Go modules"
break
else
EXIT_CODE=$?
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts (exit code: $EXIT_CODE)"
echo "This indicates that proxy.golang.org is unreachable or returning errors"
echo ""
echo "Diagnostic information:"
echo "- GOPROXY: $(go env GOPROXY)"
echo "- GOSUMDB: $(go env GOSUMDB)"
echo "- Network connectivity: checking proxy.golang.org..."
if curl -s -I --connect-timeout 5 https://proxy.golang.org/golang.org/@v/list >/dev/null 2>&1; then
echo " ✓ proxy.golang.org is reachable"
else
echo " ✗ proxy.golang.org is NOT reachable"
fi
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Pre-flight check - Validate test dependencies
run: |
echo "Validating that test dependencies are available..."
echo "This ensures go test can compile test packages without network access."
echo ""
# List all test dependencies to ensure they're in the cache
# This will fail fast if any dependencies are missing
echo "Checking test dependencies for all packages..."
if go list -test -deps ./... >/dev/null 2>&1; then
echo "✅ All test dependencies are available"
else
echo "❌ Failed to resolve test dependencies"
echo ""
echo "Attempting to show which dependencies are missing:"
go list -test -deps ./... 2>&1 || true
exit 1
fi
echo ""
echo "Module cache statistics:"
echo "- Cache directory: $(go env GOMODCACHE)"
if [ -d "$(go env GOMODCACHE)" ]; then
echo "- Cache size: $(du -sh $(go env GOMODCACHE) 2>/dev/null | cut -f1 || echo 'unknown')"
echo "- Number of cached modules: $(find $(go env GOMODCACHE) -name "go.mod" 2>/dev/null | wc -l || echo 'unknown')"
fi
- name: Run unit tests with coverage
id: run-unit-tests
run: |
set -o pipefail
# Run tests with JSON output for artifacts, but also show failures
go test -v -parallel=8 -timeout=3m -run='^Test' -tags '!integration' -coverprofile=coverage.out -json ./... | tee test-result-unit.json
# Check if tests failed by looking at JSON output
if grep -q '"Action":"fail"' test-result-unit.json; then
echo "❌ Tests failed - see output above"
exit 1
fi
# Generate coverage HTML report
go tool cover -html=coverage.out -o coverage.html
- name: Report test failures
if: failure() && steps.run-unit-tests.outcome == 'failure'
run: |
echo "## 🔍 Unit Test Failure Analysis" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Analyzing unit test results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Run the failure report script
if ./scripts/report-test-failures.sh test-result-unit.json | tee /tmp/failure-report.txt; then
echo "No failures detected in JSON output (unexpected - tests failed but no failure records found)" >> $GITHUB_STEP_SUMMARY
else
# Script found failures - add to summary
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat /tmp/failure-report.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
# Coverage reports for recent builds only - 7 days is sufficient for debugging recent changes
- name: Upload coverage report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-report
path: coverage.html
retention-days: 7
- name: Upload unit test results
if: always() # Upload even if tests fail so canary_go can track coverage
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-result-unit
path: test-result-unit.json
retention-days: 14
integration:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
test-group:
- name: "CLI Compile & Poutine"
packages: "./pkg/cli"
pattern: "^TestCompile[^W]|TestPoutine" # Exclude TestCompileWorkflows to avoid duplicates
- name: "CLI MCP Connectivity"
packages: "./pkg/cli"
pattern: "TestMCPInspectPlaywright|TestMCPGateway"
- name: "CLI MCP Other"
packages: "./pkg/cli"
pattern: "TestMCPAdd|TestMCPInspectGitHub|TestMCPServer|TestMCPConfig"
- name: "CLI Audit Logs & Firewall"
packages: "./pkg/cli"
pattern: "TestLogs|TestFirewall|TestNoStopTime|TestLocalWorkflow|^TestAudit|^TestInspect"
- name: "CLI Progress Flag" # Isolate slow test (~65s for TestProgressFlagSignature)
packages: "./pkg/cli"
pattern: "TestProgressFlagSignature"
- name: "CLI HTTP MCP Connect" # Isolate slow HTTP MCP connection tests (~43s)
packages: "./pkg/cli"
pattern: "TestConnectHTTPMCPServer"
- name: "CLI Compile Workflows" # Isolate slow workflow compilation test
packages: "./pkg/cli"
pattern: "TestCompileWorkflows_EmptyMarkdown"
- name: "CLI Security Tools" # Group security tool compilation tests
packages: "./pkg/cli"
pattern: "TestCompileWithZizmor|TestCompileWithPoutine|TestCompileWithPoutineAndZizmor"
- name: "CLI Add & List Commands"
packages: "./pkg/cli"
pattern: "^TestAdd|^TestList"
- name: "CLI Update Command"
packages: "./pkg/cli"
pattern: "^TestUpdate"
- name: "CLI Docker Build" # Isolate slow Docker tests (~43s)
packages: "./pkg/cli"
pattern: "TestDockerBuild|TestDockerImage"
- name: "CLI Completion & Other" # Remaining catch-all (reduced from original)
packages: "./pkg/cli"
pattern: "" # Catch-all for tests not matched by other CLI patterns
skip_pattern: "^TestCompile[^W]|TestPoutine|TestMCPInspectPlaywright|TestMCPGateway|TestMCPAdd|TestMCPInspectGitHub|TestMCPServer|TestMCPConfig|TestLogs|TestFirewall|TestNoStopTime|TestLocalWorkflow|TestProgressFlagSignature|TestConnectHTTPMCPServer|TestCompileWorkflows_EmptyMarkdown|TestCompileWithZizmor|TestCompileWithPoutine|TestCompileWithPoutineAndZizmor|^TestAdd|^TestList|^TestUpdate|^TestAudit|^TestInspect|TestDockerBuild|TestDockerImage"
- name: "Workflow Compiler"
packages: "./pkg/workflow"
pattern: "TestCompile|TestWorkflow|TestGenerate|TestParse"
- name: "Workflow Tools & MCP"
packages: "./pkg/workflow"
pattern: "TestMCP|TestTool|TestSkill|TestPlaywright|TestFirewall"
- name: "Workflow Validation"
packages: "./pkg/workflow"
pattern: "TestValidat|TestLock|TestError|TestWarning"
- name: "Workflow Features"
packages: "./pkg/workflow"
pattern: "SafeOutputs|CreatePullRequest|OutputLabel|HasSafeOutputs|GitHub|Git|PushToPullRequest|BuildFromAllowed|TestAgent|TestCopilot|TestCustom|TestEngine|TestModel|TestNetwork|TestOpenAI|TestProvider"
- name: "Workflow Rendering & Bundling"
packages: "./pkg/workflow"
pattern: "Render|Bundle|Script|WritePromptText"
- name: "Workflow Infra"
packages: "./pkg/workflow"
pattern: "^TestCache|TestCacheDependencies|TestCacheKey|TestValidateCache|TestPermissions|TestPackageExtractor|TestCollectPackagesFromWorkflow|Dependabot|Security|PII|Runtime|Setup|Install|Download|Version|Binary|String|Sanitize|Normalize|Trim|Clean|Format"
- name: "Workflow Actions & Containers"
packages: "./pkg/workflow"
pattern: "^TestAction[^P]|Container"
- name: "CMD Tests" # All cmd/gh-aw integration tests
packages: "./cmd/gh-aw"
pattern: ""
skip_pattern: "" # No other groups cover cmd tests
- name: "Parser Remote Fetch & Cache"
packages: "./pkg/parser"
pattern: "TestDownloadFileFromGitHub|TestResolveIncludePath|TestDownloadIncludeFromWorkflowSpec|TestImportCache"
- name: "Parser Location & Validation"
packages: "./pkg/parser"
pattern: "" # Catch-all for tests not matched by other Parser patterns
skip_pattern: "TestDownloadFileFromGitHub|TestResolveIncludePath|TestDownloadIncludeFromWorkflowSpec|TestImportCache"
- name: "Workflow Misc Part 2" # Remaining workflow tests
packages: "./pkg/workflow"
pattern: ""
skip_pattern: "TestCompile|TestWorkflow|TestGenerate|TestParse|TestMCP|TestTool|TestSkill|TestPlaywright|TestFirewall|TestValidat|TestLock|TestError|TestWarning|SafeOutputs|CreatePullRequest|OutputLabel|HasSafeOutputs|GitHub|Git|PushToPullRequest|BuildFromAllowed|Render|Bundle|Script|WritePromptText|^TestCache|TestCacheDependencies|TestCacheKey|TestValidateCache|^TestAction[^P]|Container|Dependabot|Security|PII|TestPermissions|TestPackageExtractor|TestCollectPackagesFromWorkflow|TestAgent|TestCopilot|TestCustom|TestEngine|TestModel|TestNetwork|TestOpenAI|TestProvider|String|Sanitize|Normalize|Trim|Clean|Format|Runtime|Setup|Install|Download|Version|Binary"
concurrency:
group: ci-${{ github.ref }}-integration-${{ matrix.test-group.name }}
cancel-in-progress: true
name: "Integration: ${{ matrix.test-group.name }}"
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Display Go environment
run: |
echo "Go environment:"
go env | grep -E "GOPROXY|GOSUMDB|GOMODCACHE|GOPRIVATE"
echo ""
echo "Module cache location: $(go env GOMODCACHE)"
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
# Use -x for verbose output to see what's being downloaded
if go mod download -x; then
echo "✅ Successfully downloaded Go modules"
break
else
EXIT_CODE=$?
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts (exit code: $EXIT_CODE)"
echo "This indicates that proxy.golang.org is unreachable or returning errors"
echo ""
echo "Diagnostic information:"
echo "- GOPROXY: $(go env GOPROXY)"
echo "- GOSUMDB: $(go env GOSUMDB)"
echo "- Network connectivity: checking proxy.golang.org..."
if curl -s -I --connect-timeout 5 https://proxy.golang.org/golang.org/@v/list >/dev/null 2>&1; then
echo " ✓ proxy.golang.org is reachable"
else
echo " ✗ proxy.golang.org is NOT reachable"
fi
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Pre-flight check - Validate test dependencies
run: |
echo "Validating that test dependencies are available..."
echo "This ensures go test can compile test packages without network access."
echo ""
# List all test dependencies to ensure they're in the cache
# This will fail fast if any dependencies are missing
echo "Checking test dependencies for ${{ matrix.test-group.packages }}..."
if go list -test -deps ${{ matrix.test-group.packages }} >/dev/null 2>&1; then
echo "✅ All test dependencies are available"
else
echo "❌ Failed to resolve test dependencies"
echo ""
echo "Attempting to show which dependencies are missing:"
go list -test -deps ${{ matrix.test-group.packages }} 2>&1 || true
exit 1
fi
echo ""
echo "Module cache statistics:"
echo "- Cache directory: $(go env GOMODCACHE)"
if [ -d "$(go env GOMODCACHE)" ]; then
echo "- Cache size: $(du -sh $(go env GOMODCACHE) 2>/dev/null | cut -f1 || echo 'unknown')"
echo "- Number of cached modules: $(find $(go env GOMODCACHE) -name "go.mod" 2>/dev/null | wc -l || echo 'unknown')"
fi
- name: Build gh-aw binary for integration tests
run: make build
- name: Run integration tests - ${{ matrix.test-group.name }}
id: run-tests
run: |
set -o pipefail
# Sanitize the test group name for use in filename
SAFE_NAME=$(echo "${{ matrix.test-group.name }}" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/--*/-/g')
if [ -z "${{ matrix.test-group.pattern }}" ]; then
# Catch-all group: run with -skip to exclude tests matched by other groups
if [ -n "${{ matrix.test-group.skip_pattern || '' }}" ]; then
go test -v -parallel=8 -timeout=10m -tags 'integration' -skip '${{ matrix.test-group.skip_pattern }}' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
else
go test -v -parallel=8 -timeout=10m -tags 'integration' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
fi
else
go test -v -parallel=8 -timeout=10m -tags 'integration' -run '${{ matrix.test-group.pattern }}' -json ${{ matrix.test-group.packages }} | tee "test-result-integration-${SAFE_NAME}.json"
fi
- name: Report test failures
if: failure() && steps.run-tests.outcome == 'failure'
run: |
# Sanitize the test group name to match the file created in the previous step
SAFE_NAME=$(echo "${{ matrix.test-group.name }}" | sed 's/[^a-zA-Z0-9]/-/g' | sed 's/--*/-/g')
echo "## 🔍 Test Failure Analysis" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Analyzing test results for: **${{ matrix.test-group.name }}**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Run the failure report script
if ./scripts/report-test-failures.sh "test-result-integration-${SAFE_NAME}.json" | tee /tmp/failure-report.txt; then
echo "No failures detected in JSON output (unexpected - tests failed but no failure records found)" >> $GITHUB_STEP_SUMMARY
else
# Script found failures - add to summary
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat /tmp/failure-report.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload integration test results
if: always() # Upload even if tests fail so canary_go can track coverage
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-result-integration-${{ matrix.test-group.name }}
path: test-result-integration-*.json
retention-days: 14
canary_go:
runs-on: ubuntu-latest
needs: [integration] # test dependency removed - download-artifact fetches by name, not job dependency
if: always() # Run even if some tests fail to report coverage
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: List all tests in codebase
run: |
set -euo pipefail
echo "Extracting all test function names from source files..."
./scripts/list-all-tests.sh > all-tests.txt
echo "Found $(wc -l < all-tests.txt) tests in codebase"
- name: Download all test result artifacts
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
path: test-results
pattern: test-result-*
merge-multiple: false
- name: List downloaded artifacts
run: |
set -euo pipefail
echo "Downloaded test result artifacts:"
find test-results -type f -name "*.json" | sort
echo ""
echo "Total JSON files: $(find test-results -type f -name "*.json" | wc -l)"
- name: Extract executed tests from artifacts
run: |
set -euo pipefail
echo "Extracting test names from JSON artifacts..."
./scripts/extract-executed-tests.sh test-results > executed-tests.txt
echo "Found $(wc -l < executed-tests.txt) executed tests"
- name: Compare test coverage
run: |
./scripts/compare-test-coverage.sh all-tests.txt executed-tests.txt
- name: Upload test coverage report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-coverage-analysis
path: |
all-tests.txt
executed-tests.txt
retention-days: 14
update:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-update
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Test update command (dry-run)
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Testing update command to ensure it runs successfully..."
# Run update with verbose flag to check for and apply workflow updates from source repositories
# The command may modify workflow files if upstream updates are available
./gh-aw update --verbose
echo "✅ Update command executed successfully" >> $GITHUB_STEP_SUMMARY
build:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-build
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: npm ci
run: npm ci
working-directory: ./actions/setup/js
- name: Build code
run: make build
- name: Upload Linux binary
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: gh-aw-linux-amd64
path: gh-aw
retention-days: 14
- name: Rebuild lock files
run: make recompile
env:
GH_TOKEN: ${{ github.token }}
build-wasm:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-build-wasm
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
# Use -x for verbose output to see what's being downloaded
if go mod download -x; then
echo "✅ Successfully downloaded Go modules"
break
else
EXIT_CODE=$?
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts (exit code: $EXIT_CODE)"
echo "This indicates that proxy.golang.org is unreachable or returning errors"
echo ""
echo "Diagnostic information:"
echo "- GOPROXY: $(go env GOPROXY)"
echo "- GOSUMDB: $(go env GOSUMDB)"
echo "- Network connectivity: checking proxy.golang.org..."
if curl -s -I --connect-timeout 5 https://proxy.golang.org/golang.org/@v/list >/dev/null 2>&1; then
echo " ✓ proxy.golang.org is reachable"
else
echo " ✗ proxy.golang.org is NOT reachable"
fi
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build WebAssembly binary
run: make build-wasm
- name: Report binary size
run: |
echo "## WebAssembly Build" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if ls gh-aw.wasm 1>/dev/null 2>&1; then
SIZE=$(stat --format="%s" gh-aw.wasm)
SIZE_MB=$(awk "BEGIN {printf \"%.2f\", $SIZE/1048576}")
echo "✅ Build succeeded" >> $GITHUB_STEP_SUMMARY
echo "- **Binary:** gh-aw.wasm" >> $GITHUB_STEP_SUMMARY
echo "- **Size:** ${SIZE_MB} MB (${SIZE} bytes)" >> $GITHUB_STEP_SUMMARY
else
echo "❌ No .wasm binary found" >> $GITHUB_STEP_SUMMARY
ls -la *.wasm 2>/dev/null || echo "No wasm files in working directory" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Run wasm golden tests (Go string API)
run: make test-wasm-golden
- name: Set up Node.js
uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4
with:
node-version: '20'
- name: Run wasm binary golden tests (Node.js)
run: node scripts/test-wasm-golden.mjs
validate-yaml:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Check for ANSI escape sequences in YAML files
run: |
echo "🔍 Scanning YAML workflow files for ANSI escape sequences..."
# Find all YAML files in .github/workflows directory
YAML_FILES=$(find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) | sort)
# Track if any ANSI codes are found
FOUND_ANSI=0
# Check each file for ANSI escape sequences
for file in $YAML_FILES; do
# Use grep to find ANSI escape sequences (ESC [ ... letter)
# The pattern matches: \x1b followed by [ followed by optional digits/semicolons followed by a letter
if grep -P '\x1b\[[0-9;]*[a-zA-Z]' "$file" > /dev/null 2>&1; then
echo "❌ ERROR: Found ANSI escape sequences in: $file"
echo ""
echo "Lines with ANSI codes:"
grep -n -P '\x1b\[[0-9;]*[a-zA-Z]' "$file" || true
echo ""
FOUND_ANSI=1
fi
done
if [ $FOUND_ANSI -eq 1 ]; then
echo ""
echo "💡 ANSI escape sequences detected in YAML files!"
echo ""
echo "These are terminal color codes that break YAML parsing."
echo "Common causes:"
echo " - Copy-pasting from colored terminal output"
echo " - Text editors preserving ANSI codes"
echo " - Scripts generating colored output"
echo ""
echo "To fix:"
echo " 1. Remove the ANSI codes from the affected files"
echo " 2. Run 'make recompile' to regenerate workflow files"
echo " 3. Use '--no-color' flags when capturing command output"
echo ""
exit 1
fi
echo "✅ No ANSI escape sequences found in YAML files"
- name: Check for release-compiled lock files
run: |
echo "🔍 Checking .lock.yml files for release build compilation..."
# Find all .lock.yml files in the repository
LOCK_FILES=$(find . -type f -name "*.lock.yml" | sort)
if [ -z "$LOCK_FILES" ]; then
echo "⚠️ WARNING: No .lock.yml files found"
exit 0
fi
# Track if any release-compiled files are found
FOUND_RELEASE=0
# Check each file for version numbers in the header
# Release builds include version like: "# This file was automatically generated by gh-aw (v1.0.0). DO NOT EDIT."
# Dev builds do not: "# This file was automatically generated by gh-aw. DO NOT EDIT."
for file in $LOCK_FILES; do
# Look for the pattern: "by gh-aw (v" or "by gh-aw (0" or similar version patterns
# This matches versions like (v1.0.0), (0.1.0), etc.
if grep -E '# This file was automatically generated by gh-aw \([v0-9]' "$file" > /dev/null 2>&1; then
echo "❌ ERROR: Found release-compiled lock file: $file"
echo ""
echo "Header line:"
grep -E '# This file was automatically generated by gh-aw \([v0-9]' "$file" || true
echo ""
FOUND_RELEASE=1
fi
done
if [ $FOUND_RELEASE -eq 1 ]; then
echo ""
echo "💡 Lock files should NOT be compiled with a release build!"
echo ""
echo "Lock files in the repository must be compiled with development builds."
echo "Release builds include version numbers in the header, which should only"
echo "appear in released binaries, not in source-controlled workflow files."
echo ""
echo "To fix:"
echo " 1. Build the CLI with 'make build' (dev build, no release flag)"
echo " 2. Run 'make recompile' to regenerate all lock files"
echo " 3. Commit the updated lock files"
echo ""
echo "The release build flag is only set during the release process via:"
echo " scripts/build-release.sh (sets -X main.isRelease=true)"
echo ""
exit 1
fi
echo "✅ All lock files compiled with development build (no version in header)"
- name: Check agent file URLs use main branch
run: |
echo "🔍 Checking .github/agents/agentic-workflows.agent.md for correct branch URLs..."
AGENT_FILE=".github/agents/agentic-workflows.agent.md"
if [ ! -f "$AGENT_FILE" ]; then
echo "⚠️ WARNING: $AGENT_FILE not found, skipping check"
exit 0
fi
# Check for URLs that don't use 'main' branch
# Pattern matches: https://github.com/github/gh-aw/blob/{anything-except-main}/
# Uses negative lookahead to exclude 'main'
INVALID_URLS=$(grep -n 'https://github.com/github/gh-aw/blob/' "$AGENT_FILE" | grep -v '/blob/main/' || true)
if [ -n "$INVALID_URLS" ]; then
echo "❌ ERROR: Found URLs not using 'main' branch in $AGENT_FILE"
echo ""
echo "Lines with invalid URLs:"
echo "$INVALID_URLS"
echo ""
echo "💡 All GitHub URLs in agent files must reference the 'main' branch!"
echo ""
echo "URLs should use the pattern:"
echo " https://github.com/github/gh-aw/blob/main/.github/aw/..."
echo ""
echo "To fix:"
echo " 1. Edit $AGENT_FILE"
echo " 2. Replace all 'blob/{commit-hash}/' or 'blob/{tag}/' with 'blob/main/'"
echo " 3. Commit the updated file"
echo ""
exit 1
fi
echo "✅ All URLs in $AGENT_FILE correctly use 'main' branch"
js:
runs-on: ubuntu-latest
needs: validate-yaml
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-js
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Install npm dependencies
run: cd actions/setup/js && npm ci
- name: Setup prompt templates for tests
run: |
mkdir -p /opt/gh-aw/prompts
cp actions/setup/md/*.md /opt/gh-aw/prompts/
- name: Run tests
run: cd actions/setup/js && npm test
js-integration-live-api:
runs-on: ubuntu-latest
needs: validate-yaml
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-js-integration-live-api
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Install npm dependencies
run: cd actions/setup/js && npm ci
- name: Run live GitHub API integration test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "## 🔍 Live GitHub API Integration Test" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -z "$GITHUB_TOKEN" ]; then
echo "⚠️ GITHUB_TOKEN not available - test will be skipped" >> $GITHUB_STEP_SUMMARY
echo "ℹ️ This is expected in forks or when secrets are not available" >> $GITHUB_STEP_SUMMARY
cd actions/setup/js && npm test -- frontmatter_hash_github_api.test.cjs
else
echo "✅ GITHUB_TOKEN available - running live API test" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cd actions/setup/js && npm test -- frontmatter_hash_github_api.test.cjs
echo "" >> $GITHUB_STEP_SUMMARY
echo "✨ Live API test completed successfully" >> $GITHUB_STEP_SUMMARY
fi
bench:
# Only run benchmarks on main branch for performance tracking
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-bench
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Run benchmarks
run: make bench
- name: Display benchmark summary
run: |
echo "## 📊 Benchmark Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
# Show compiler benchmarks from the results
grep "BenchmarkCompile" bench_results.txt | head -20 >> $GITHUB_STEP_SUMMARY || echo "No benchmark results found" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📁 Full results saved to artifact: benchmark-results" >> $GITHUB_STEP_SUMMARY
# Benchmark results for performance trend analysis - 14 days allows comparison across multiple runs
- name: Save benchmark results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: benchmark-results
path: bench_results.txt
if-no-files-found: ignore
retention-days: 14
lint-go:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-lint-go
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
fetch-depth: 0 # Fetch all history for incremental linting
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
# Go formatting check (fast, no deps needed)
- name: Check Go formatting
run: |
unformatted=$(go fmt ./...)
if [ -n "$unformatted" ]; then
echo "❌ Code is not formatted. Run 'make fmt' to fix." >> $GITHUB_STEP_SUMMARY
echo "Unformatted files:" >> $GITHUB_STEP_SUMMARY
echo "$unformatted" >> $GITHUB_STEP_SUMMARY
echo ""
echo "To fix this locally, run:"
echo " make fmt"
echo ""
echo "Or format individual files with:"
echo " go fmt ./path/to/file.go"
exit 1
fi
echo "✅ Go formatting check passed" >> $GITHUB_STEP_SUMMARY
# Install golangci-lint binary (avoiding GPL dependencies)
# Downloads pre-built binary from GitHub releases instead of using go install
- name: Install golangci-lint
run: |
GOLANGCI_LINT_VERSION="v2.8.0"
GOOS=$(go env GOOS)
GOARCH=$(go env GOARCH)
GOPATH=$(go env GOPATH)
BINARY_NAME="golangci-lint"
echo "Installing golangci-lint $GOLANGCI_LINT_VERSION for $GOOS/$GOARCH..."
DOWNLOAD_URL="https://github.com/golangci/golangci-lint/releases/download/$GOLANGCI_LINT_VERSION/golangci-lint-${GOLANGCI_LINT_VERSION#v}-$GOOS-$GOARCH.tar.gz"
ARCHIVE="/tmp/golangci-lint.tar.gz"
# Save archive to disk first so we can verify it before extracting.
# --fail causes curl to exit non-zero on HTTP errors (4xx/5xx),
# --retry 3 retries transient network failures automatically.
curl --fail --retry 3 -sSL "$DOWNLOAD_URL" -o "$ARCHIVE"
tar -xz -C /tmp -f "$ARCHIVE"
mkdir -p "$GOPATH/bin"
mv /tmp/golangci-lint-*/$BINARY_NAME "$GOPATH/bin/$BINARY_NAME"
chmod +x "$GOPATH/bin/$BINARY_NAME"
echo "✓ golangci-lint $GOLANGCI_LINT_VERSION installed"
"$GOPATH/bin/$BINARY_NAME" version
# Run golangci-lint via Makefile for consistency
# Uses incremental linting on PRs for faster CI (50-75% speedup)
# Performance optimizations in .golangci.yml:
# - timeout: 5m prevents hanging
# - modules-download-mode: readonly uses cached modules only
- name: Run golangci-lint
run: |
export PATH="$PATH:$(go env GOPATH)/bin"
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Incremental linting on PRs - only check changed files
# This provides 50-75% faster linting on typical PRs
BASE_REF="origin/${{ github.base_ref }}"
if git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then
echo "Using incremental lint against $BASE_REF"
make golint-incremental BASE_REF="$BASE_REF"
else
echo "⚠️ Base ref $BASE_REF not found, falling back to full lint"
make golint
fi
else
# Full scan on main branch to ensure comprehensive coverage
make golint
fi
# Error message linting (requires Go only)
- name: Lint error messages
run: make lint-errors
lint-js:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-lint-js
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Node.js
id: setup-node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
with:
node-version: "24"
cache: npm
cache-dependency-path: actions/setup/js/package-lock.json
- name: Report Node cache status
run: |
if [ "${{ steps.setup-node.outputs.cache-hit }}" == "true" ]; then
echo "✅ Node cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Node cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Install npm dependencies
run: cd actions/setup/js && npm ci
# JavaScript and JSON formatting checks
- name: Lint JavaScript files
run: make lint-cjs
- name: Check JSON formatting
run: make fmt-check-json
audit:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-audit
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Run dependency audit (human-readable)
run: |
echo "## Dependency Health Audit" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
./gh-aw upgrade --audit 2>&1 | tee audit_output.txt || true
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
head -100 audit_output.txt >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
- name: Run dependency audit (JSON)
id: audit_json
run: |
# Run audit with JSON output for agent-friendly parsing
./gh-aw upgrade --audit --json > audit.json 2>&1
# Display summary in GitHub Actions
echo "✅ Dependency audit completed" >> $GITHUB_STEP_SUMMARY
# Extract key metrics
TOTAL_DEPS=$(jq '.summary.total_dependencies' audit.json)
OUTDATED=$(jq '.summary.outdated_count' audit.json)
SECURITY=$(jq '.summary.security_advisories' audit.json)
V0_PERCENT=$(jq '.summary.v0_percentage' audit.json)
echo "📊 **Audit Results:**" >> $GITHUB_STEP_SUMMARY
echo "- Total dependencies: $TOTAL_DEPS" >> $GITHUB_STEP_SUMMARY
echo "- Outdated: $OUTDATED" >> $GITHUB_STEP_SUMMARY
echo "- Security advisories: $SECURITY" >> $GITHUB_STEP_SUMMARY
echo "- v0.x exposure: ${V0_PERCENT}%" >> $GITHUB_STEP_SUMMARY
- name: Upload audit results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: dependency-audit
path: |
audit.json
audit_output.txt
retention-days: 30
actions-build:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-actions-build
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build actions
run: make actions-build
- name: Validate actions
run: make actions-validate
fuzz:
# Only run fuzz tests on main branch (10s is insufficient for PRs)
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-fuzz
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Run fuzz tests
run: |
set -o pipefail
# Create directory for fuzz results
mkdir -p fuzz-results
# Helper function to run fuzz test and handle context deadline
# Go fuzz tests can exit with status 1 and "context deadline exceeded" when
# they reach the -fuzztime limit. We treat this as expected success to allow
# all fuzz targets to run instead of stopping at the first timeout.
run_fuzz_test() {
local fuzz_name=$1
local package=$2
local output_file="fuzz-results/${fuzz_name}.txt"
echo "Running ${fuzz_name}..."
if go test -run='^$' -fuzz="^${fuzz_name}$" -fuzztime=10s "${package}" 2>&1 | tee "${output_file}"; then
echo "✅ ${fuzz_name} completed successfully"
return 0
else
# Check if the failure was due to context deadline (expected)
if grep -q "context deadline exceeded" "${output_file}"; then
echo "✅ ${fuzz_name} completed (context deadline reached as expected)"
return 0
else
echo "❌ ${fuzz_name} failed with unexpected error"
return 1
fi
fi
}
# Run fuzz tests and capture output
run_fuzz_test "FuzzParseFrontmatter" "./pkg/parser/"
run_fuzz_test "FuzzScheduleParser" "./pkg/parser/"
run_fuzz_test "FuzzRuntimeImportExpressionValidation" "./pkg/parser/"
run_fuzz_test "FuzzRuntimeImportProcessExpressions" "./pkg/parser/"
run_fuzz_test "FuzzExpressionParser" "./pkg/workflow/"
run_fuzz_test "FuzzMentionsFiltering" "./pkg/workflow/"
run_fuzz_test "FuzzSanitizeOutput" "./pkg/workflow/"
run_fuzz_test "FuzzSanitizeIncomingText" "./pkg/workflow/"
run_fuzz_test "FuzzSanitizeLabelContent" "./pkg/workflow/"
run_fuzz_test "FuzzWrapExpressionsInTemplateConditionals" "./pkg/workflow/"
run_fuzz_test "FuzzYAMLParsing" "./pkg/workflow/"
run_fuzz_test "FuzzTemplateRendering" "./pkg/workflow/"
run_fuzz_test "FuzzInputValidation" "./pkg/workflow/"
run_fuzz_test "FuzzNetworkPermissions" "./pkg/workflow/"
run_fuzz_test "FuzzSafeJobConfig" "./pkg/workflow/"
run_fuzz_test "FuzzParseLabelTriggerShorthand" "./pkg/workflow/"
run_fuzz_test "FuzzExpandLabelTriggerShorthand" "./pkg/workflow/"
run_fuzz_test "FuzzValidateNoTemplateInjection" "./pkg/workflow/"
run_fuzz_test "FuzzRemoveHeredocContent" "./pkg/workflow/"
run_fuzz_test "FuzzMarkdownCodeRegionBalancer" "./pkg/workflow/"
run_fuzz_test "FuzzParseTriggerShorthand" "./pkg/workflow/"
run_fuzz_test "FuzzTriggerIRToYAMLMap" "./pkg/workflow/"
run_fuzz_test "FuzzParseInputDefinition" "./pkg/workflow/"
run_fuzz_test "FuzzParseInputDefinitions" "./pkg/workflow/"
# Copy fuzz corpus data (testdata/fuzz directories)
echo "Copying fuzz corpus data..."
find ./pkg -path "*/testdata/fuzz" -type d | while read -r dir; do
pkg_name=$(echo "$dir" | sed 's|^\./pkg/||' | sed 's|/testdata/fuzz$||')
echo "Copying corpus from $dir to fuzz-results/corpus/$pkg_name/"
mkdir -p "fuzz-results/corpus/$pkg_name"
cp -r "$dir"/* "fuzz-results/corpus/$pkg_name/" 2>/dev/null || echo "No corpus data in $dir"
done
- name: Upload fuzz test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: fuzz-results
path: fuzz-results/
retention-days: 14
security:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-security
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Run security regression tests
run: make test-security
security-scan:
# Only run security scans on main branch to reduce PR overhead
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10 # Prevent jobs from hanging indefinitely
permissions:
contents: read
strategy:
fail-fast: false
matrix:
tool:
- name: zizmor
flag: --zizmor
- name: actionlint
flag: --actionlint
- name: poutine
flag: --poutine
concurrency:
group: ci-${{ github.ref }}-security-scan-${{ matrix.tool.name }}
cancel-in-progress: true
name: "Security Scan: ${{ matrix.tool.name }}"
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Build gh-aw
run: make build
- name: Run ${{ matrix.tool.name }} security scan on poem workflow
run: ./gh-aw compile poem-bot ${{ matrix.tool.flag }} --verbose
logs-token-check:
if: false
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
concurrency:
group: ci-${{ github.ref }}-logs-token-check
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Build gh-aw
run: make build
- name: Run logs command with JSON output
id: logs_check
run: |
set -e # Fail on first error
# Run the logs command and capture only stdout (JSON output)
# stderr is not redirected, so warning messages go to console
./gh-aw logs smoke-copilot -c 2 --json --verbose > logs_output.json
# Display the output for debugging
echo "Logs command output:"
cat logs_output.json
# Check if the JSON structure is valid
echo "## Validating JSON Structure"
# Check if token count is found in the JSON output
if jq -e '.summary.total_tokens' logs_output.json > /dev/null 2>&1; then
TOKEN_COUNT=$(jq '.summary.total_tokens' logs_output.json)
echo "✅ Token count found: $TOKEN_COUNT"
# Validate that token count is greater than 0
if [ "$TOKEN_COUNT" -gt 0 ]; then
echo "✅ Token count is greater than 0: $TOKEN_COUNT"
echo "token_count=$TOKEN_COUNT" >> $GITHUB_OUTPUT
else
echo "❌ Token count is 0 - expected tokens to be parsed from logs"
exit 1
fi
else
echo "❌ Token count not found in JSON output"
exit 1
fi
# Check if runs array exists (even if empty)
if jq -e '.runs' logs_output.json > /dev/null 2>&1; then
RUNS_COUNT=$(jq '.runs | length' logs_output.json)
echo "✅ Runs array found: $RUNS_COUNT runs"
else
echo "❌ Runs array not found in JSON output"
exit 1
fi
# If there are runs, validate that key fields are resolved
if [ "$RUNS_COUNT" -gt 0 ]; then
# Check if agent (engine_id) field exists in first run
if jq -e '.runs[0] | has("agent")' logs_output.json > /dev/null 2>&1; then
AGENT=$(jq -r '.runs[0].agent // "null"' logs_output.json)
echo "✅ Agent field found in run: $AGENT"
else
echo "❌ Agent field not found in run data"
exit 1
fi
# Check if workflow_path field exists in first run
if jq -e '.runs[0] | has("workflow_path")' logs_output.json > /dev/null 2>&1; then
WORKFLOW_PATH=$(jq -r '.runs[0].workflow_path // "null"' logs_output.json)
echo "✅ Workflow path field found in run: $WORKFLOW_PATH"
else
echo "❌ Workflow path field not found in run data"
exit 1
fi
# Check if workflow_name is present
if jq -e '.runs[0].workflow_name' logs_output.json > /dev/null 2>&1; then
WORKFLOW_NAME=$(jq -r '.runs[0].workflow_name' logs_output.json)
echo "✅ Workflow name found in run: $WORKFLOW_NAME"
else
echo "❌ Workflow name not found in run data"
exit 1
fi
else
echo "ℹ️ No runs found to validate (this is ok)"
fi
echo "✅ All JSON structure validations passed"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
mcp-server-compile-test:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-mcp-server-compile-test
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Build gh-aw binary
run: make build
- name: Create test workflow with error
run: |
mkdir -p .github/workflows
cat > .github/workflows/test-invalid.md << 'EOF'
---
on: push
engine: copilot
invalid_field: this will cause an error
---
# Test Invalid Workflow
This workflow has an invalid field that will cause a compilation error.
EOF
- name: Test MCP server compile tool
run: |
# Create a test script using the MCP Go SDK
cat > test_mcp_compile.go << 'GOEOF'
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
func main() {
// Create MCP client
client := mcp.NewClient(&mcp.Implementation{
Name: "ci-test-client",
Version: "1.0.0",
}, nil)
// Start the MCP server as a subprocess with absolute path
binaryPath := "./gh-aw"
serverCmd := exec.Command(binaryPath, "mcp-server", "--cmd", binaryPath)
transport := &mcp.CommandTransport{Command: serverCmd}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Connect to the server
session, err := client.Connect(ctx, transport, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect to MCP server: %v\n", err)
os.Exit(1)
}
defer session.Close()
fmt.Println("✅ Successfully connected to MCP server")
// Call the compile tool with the invalid workflow
params := &mcp.CallToolParams{
Name: "compile",
Arguments: map[string]any{
"workflows": []string{"test-invalid.md"},
},
}
result, err := session.CallTool(ctx, params)
if err != nil {
fmt.Fprintf(os.Stderr, "MCP tool call returned error (this is expected): %v\n", err)
// Check if the error contains expected error information
fmt.Println("✅ Compile tool correctly returned an error")
os.Exit(0)
}
// Get the result content
if len(result.Content) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected non-empty result from compile tool")
os.Exit(1)
}
textContent, ok := result.Content[0].(*mcp.TextContent)
if !ok {
fmt.Fprintln(os.Stderr, "❌ Expected text content from compile tool")
os.Exit(1)
}
fmt.Printf("Compile tool output:\n%s\n", textContent.Text)
// Parse the JSON output to check for errors
var compileResults []map[string]any
if err := json.Unmarshal([]byte(textContent.Text), &compileResults); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse JSON output: %v\n", err)
os.Exit(1)
}
// Check if the workflow is marked as invalid
if len(compileResults) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected at least one workflow result")
os.Exit(1)
}
result0 := compileResults[0]
valid, ok := result0["valid"].(bool)
if !ok {
fmt.Fprintln(os.Stderr, "❌ Expected 'valid' field in result")
os.Exit(1)
}
if valid {
fmt.Fprintln(os.Stderr, "❌ Expected workflow to be invalid")
os.Exit(1)
}
// Check that errors field exists and has at least one error
errors, ok := result0["errors"].([]any)
if !ok || len(errors) == 0 {
fmt.Fprintln(os.Stderr, "❌ Expected errors array with at least one error")
os.Exit(1)
}
fmt.Println("✅ Compile tool correctly reported validation errors:")
errorsJSON, _ := json.MarshalIndent(errors, " ", " ")
fmt.Printf(" %s\n", string(errorsJSON))
os.Exit(0)
}
GOEOF
# Run the test
go run test_mcp_compile.go
- name: Report test results
if: always()
run: |
echo "## MCP Server Compile Tool Test" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This test verifies that:" >> $GITHUB_STEP_SUMMARY
echo "1. The gh-aw MCP server can be started successfully" >> $GITHUB_STEP_SUMMARY
echo "2. The compile tool can be invoked through the MCP server" >> $GITHUB_STEP_SUMMARY
echo "3. The compile tool correctly detects and reports validation errors" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Test completed successfully" >> $GITHUB_STEP_SUMMARY
cross-platform-build:
name: Build & Test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- windows-latest
concurrency:
group: ci-${{ github.ref }}-cross-platform-${{ matrix.os }}
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
shell: bash
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
shell: bash
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Create test workflow
shell: bash
run: |
mkdir -p .github/workflows
cat > .github/workflows/test-cross-platform.md << 'EOF'
---
on: push
engine: copilot
---
# Test Workflow for Cross-Platform CI
This is a simple test workflow to verify the compile command works correctly.
## Task
Echo hello world.
EOF
- name: Test compile command
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "Testing compile command on ${{ matrix.os }}..."
# Determine binary name based on OS
if [[ "$RUNNER_OS" == "Windows" ]]; then
BINARY="./gh-aw.exe"
else
BINARY="./gh-aw"
fi
# Verify binary exists
if [ ! -f "$BINARY" ]; then
echo "❌ Binary not found: $BINARY"
ls -la
exit 1
fi
# Run compile command
"$BINARY" compile test-cross-platform --verbose
# Check if lock file was generated
if [ -f ".github/workflows/test-cross-platform.lock.yml" ]; then
echo "✅ Compile succeeded - lock file generated"
else
echo "❌ Compile failed - no lock file generated"
exit 1
fi
echo "## Cross-Platform Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Successfully compiled workflow on ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Platform:** ${{ matrix.os }}" >> $GITHUB_STEP_SUMMARY
echo "**Binary:** $BINARY" >> $GITHUB_STEP_SUMMARY
echo "**Go version:** $(go version)" >> $GITHUB_STEP_SUMMARY
- name: Clean up test files
if: always()
shell: bash
run: |
rm -f .github/workflows/test-cross-platform.md
rm -f .github/workflows/test-cross-platform.lock.yml
alpine-container-test:
name: Alpine Container Test
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-alpine-container
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies with retry
run: |
set -e
MAX_RETRIES=3
RETRY_DELAY=5
for i in $(seq 1 $MAX_RETRIES); do
echo "Attempt $i of $MAX_RETRIES: Downloading Go modules..."
if go mod download; then
echo "✅ Successfully downloaded Go modules"
break
else
if [ $i -eq $MAX_RETRIES ]; then
echo "❌ Failed to download Go modules after $MAX_RETRIES attempts"
echo "This may indicate that proxy.golang.org is unreachable"
echo "Please check network connectivity or consider vendoring dependencies"
exit 1
fi
echo "⚠️ Download failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
fi
done
- name: Verify dependencies
run: go mod verify
- name: Build Linux binary for Alpine
run: make build-linux
- name: Build Alpine Docker image
run: |
echo "Building Alpine Docker image..."
docker build -t gh-aw-alpine:test \
--build-arg BINARY=gh-aw-linux-amd64 \
-f Dockerfile .
echo "✅ Alpine Docker image built successfully"
- name: Test Docker image basic commands
run: |
echo "Testing Docker image basic commands..."
docker run --rm gh-aw-alpine:test --version
docker run --rm gh-aw-alpine:test --help
echo "✅ Basic commands work"
- name: Create test workflow in container
run: |
echo "Creating test workflow file..."
mkdir -p test-workspace/.github/workflows
cat > test-workspace/.github/workflows/test-alpine.md << 'EOF'
---
on: push
engine: copilot
---
# Test Workflow for Alpine Container
This is a simple test workflow to verify the compile command works correctly in Alpine container.
## Task
Echo hello from Alpine container.
EOF
echo "✅ Test workflow created"
- name: Run compile through Alpine container
run: |
echo "Running compile command through Alpine container..."
docker run --rm \
-v "$(pwd)/test-workspace:/workspace" \
-w /workspace \
gh-aw-alpine:test compile test-alpine --verbose
echo "✅ Compile command executed"
- name: Verify lock file generation
run: |
echo "Verifying lock file was generated..."
if [ -f "test-workspace/.github/workflows/test-alpine.lock.yml" ]; then
echo "✅ Lock file generated successfully"
echo ""
echo "Lock file contents:"
head -20 test-workspace/.github/workflows/test-alpine.lock.yml
else
echo "❌ Lock file not found"
ls -la test-workspace/.github/workflows/
exit 1
fi
- name: Generate test summary
if: always()
run: |
echo "## Alpine Container Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This test verifies that:" >> $GITHUB_STEP_SUMMARY
echo "1. The Alpine Docker image can be built successfully" >> $GITHUB_STEP_SUMMARY
echo "2. The gh-aw binary works correctly in Alpine Linux" >> $GITHUB_STEP_SUMMARY
echo "3. The compile command can process workflows in the container" >> $GITHUB_STEP_SUMMARY
echo "4. Lock files are generated correctly" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "test-workspace/.github/workflows/test-alpine.lock.yml" ]; then
echo "✅ All tests passed successfully" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Lock file generation failed" >> $GITHUB_STEP_SUMMARY
fi
- name: Clean up test files
if: always()
run: |
rm -rf test-workspace
safe-outputs-conformance:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Run Safe Outputs Conformance Checker
id: conformance
continue-on-error: true
run: |
echo "## Safe Outputs Conformance Check" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Run the conformance checker and capture output
if ./scripts/check-safe-outputs-conformance.sh > conformance-output.txt 2>&1; then
echo "✅ All conformance checks passed" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=0
else
EXIT_CODE=$?
if [ $EXIT_CODE -eq 2 ]; then
echo "⚠️ Critical conformance issues found (treated as warning)" >> $GITHUB_STEP_SUMMARY
elif [ $EXIT_CODE -eq 1 ]; then
echo "⚠️ High priority conformance issues found (treated as warning)" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Conformance check completed with warnings" >> $GITHUB_STEP_SUMMARY
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Conformance Check Output" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat conformance-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
# Also output to console for visibility
echo "=== Conformance Check Results ==="
cat conformance-output.txt
# Always succeed (treat as warning only)
exit 0
- name: Upload conformance report
if: always()
uses: actions/upload-artifact@v4
with:
name: safe-outputs-conformance-report
path: conformance-output.txt
retention-days: 7
integration-add:
name: Integration Add Workflows
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}-integration-add
cancel-in-progress: true
steps:
- name: Checkout code
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
- name: Set up Go
id: setup-go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6
with:
go-version-file: go.mod
cache: true
- name: Report Go cache status
run: |
if [ "${{ steps.setup-go.outputs.cache-hit }}" == "true" ]; then
echo "✅ Go cache hit" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Go cache miss" >> $GITHUB_STEP_SUMMARY
fi
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
- name: Build gh-aw binary
run: make build
- name: Verify gh-aw binary
run: |
./gh-aw --help
./gh-aw version
- name: Clone githubnext/agentics repository
run: |
echo "Cloning githubnext/agentics repository..."
cd /tmp
git clone --depth 1 --filter=blob:none https://github.com/githubnext/agentics.git
echo "✅ Repository cloned successfully"
- name: List workflows from agentics
id: list-workflows
run: |
echo "Listing workflow files from githubnext/agentics..."
cd /tmp/agentics/workflows
# Get list of all .md workflow files (just the names without .md extension)
WORKFLOWS=$(ls *.md | sed 's/\.md$//')
echo "Found workflows:"
echo "$WORKFLOWS"
# Count workflows
WORKFLOW_COUNT=$(echo "$WORKFLOWS" | wc -l)
echo "Total workflows found: $WORKFLOW_COUNT"
# Save workflow list for next step
echo "$WORKFLOWS" > /tmp/workflow-list.txt
echo "workflow_count=$WORKFLOW_COUNT" >> $GITHUB_OUTPUT
- name: Compare gh aw list with git clone
run: |
set -e
echo "## Comparing 'gh aw list' output with git clone results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# List workflows using gh aw list command with custom path
echo "Running: ./gh-aw list --repo githubnext/agentics --path workflows --json"
./gh-aw list --repo githubnext/agentics --path workflows --json > /tmp/gh-aw-list.json
# Extract workflow names from JSON output
echo "Extracting workflow names from gh aw list output..."
jq -r '.[].workflow' /tmp/gh-aw-list.json | sort > /tmp/gh-aw-workflows.txt
# Get workflow names from git clone (already in /tmp/workflow-list.txt)
echo "Sorting git clone workflow list..."
sort /tmp/workflow-list.txt > /tmp/git-workflows-sorted.txt
# Display both lists
echo "### Workflows from 'gh aw list --repo githubnext/agentics --path workflows'" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/gh-aw-workflows.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Workflows from git clone" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/git-workflows-sorted.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Compare the two lists
if diff -u /tmp/git-workflows-sorted.txt /tmp/gh-aw-workflows.txt > /tmp/diff-output.txt; then
echo "✅ **SUCCESS**: Workflow lists match!" >> $GITHUB_STEP_SUMMARY
echo "The 'gh aw list' command returned the same workflows as the git clone." >> $GITHUB_STEP_SUMMARY
echo ""
echo "✅ Workflow lists match!"
else
echo "❌ **FAILURE**: Workflow lists do not match!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Differences" >> $GITHUB_STEP_SUMMARY
echo '```diff' >> $GITHUB_STEP_SUMMARY
cat /tmp/diff-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo ""
echo "❌ Workflow lists do not match!"
echo "Differences:"
cat /tmp/diff-output.txt
exit 1
fi
- name: Add workflows one by one
id: add-workflows
env:
GH_TOKEN: ${{ github.token }}
run: |
cd /home/runner/work/gh-aw/gh-aw
echo "## Adding Workflows from githubnext/agentics" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Workflow | Status | Details |" >> $GITHUB_STEP_SUMMARY
echo "|----------|--------|---------|" >> $GITHUB_STEP_SUMMARY
SUCCESS_COUNT=0
FAILURE_COUNT=0
# Read workflow list
while IFS= read -r workflow; do
echo "Processing workflow: $workflow"
# Try to add the workflow using gh aw add
if ./gh-aw add "githubnext/agentics/$workflow" --force 2>&1 | tee /tmp/add-${workflow}.log; then
echo "✅ Successfully added: $workflow"
echo "| $workflow | ✅ Success | Added successfully |" >> $GITHUB_STEP_SUMMARY
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
EXIT_CODE=$?
echo "❌ Failed to add: $workflow (exit code: $EXIT_CODE)"
# Extract error message from log
ERROR_MSG=$(tail -5 /tmp/add-${workflow}.log | tr '\n' ' ' | cut -c1-100)
echo "| $workflow | ❌ Failed | Exit code: $EXIT_CODE - ${ERROR_MSG}... |" >> $GITHUB_STEP_SUMMARY
FAILURE_COUNT=$((FAILURE_COUNT + 1))
fi
echo "---"
done < /tmp/workflow-list.txt
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Summary" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Successful: $SUCCESS_COUNT" >> $GITHUB_STEP_SUMMARY
echo "- ❌ Failed: $FAILURE_COUNT" >> $GITHUB_STEP_SUMMARY
echo "- Total: ${{ steps.list-workflows.outputs.workflow_count }}" >> $GITHUB_STEP_SUMMARY
echo "success_count=$SUCCESS_COUNT" >> $GITHUB_OUTPUT
echo "failure_count=$FAILURE_COUNT" >> $GITHUB_OUTPUT
# Report overall result
echo ""
echo "====================================="
echo "Integration Test Results"
echo "====================================="
echo "Successful additions: $SUCCESS_COUNT"
echo "Failed additions: $FAILURE_COUNT"
echo "Total workflows: ${{ steps.list-workflows.outputs.workflow_count }}"
echo "====================================="
- name: Check for added workflows
run: |
echo "Checking for added workflow files..."
if [ -d ".github/workflows" ]; then
echo "Found workflows directory"
ls -la .github/workflows/*.md 2>/dev/null | head -20 || echo "No .md files found"
else
echo "No .github/workflows directory found"
fi
- name: Test result summary
if: always()
run: |
echo "=== Agentics Workflows Integration Test Summary ==="
echo "This test validates that gh-aw can successfully add workflows"
echo "from the githubnext/agentics repository."
echo ""
echo "Test completed with:"
echo "- Success count: ${{ steps.add-workflows.outputs.success_count }}"
echo "- Failure count: ${{ steps.add-workflows.outputs.failure_count }}"