GitHub Actions CI/CD

Automate skill execution in your CI/CD pipeline using the Clanker API and GitHub Actions.

Overview

Integrate Clanker into your development pipeline:

  • Generate documentation on every release
  • Run code reviews on pull requests
  • Create changelogs automatically
  • Validate code quality before merge

Skills are run through the REST API (POST /api/v1/skills/:slug/run); each run produces an execution you poll for completion and an artifact you can download. For multi-step pipelines, launch a workflow (POST /api/v1/workflows/:id/launch) instead.

Prerequisites

  1. Clanker account
  2. API key for authentication
  3. Skills installed in your Clanker library

Basic Setup

1. Store Credentials

Add your Clanker credentials as GitHub secrets:

  1. Go to your repository > Settings > Secrets and variables > Actions
  2. Add new secrets:
    • CLANKER_API_KEY - Your API key

2. Create Workflow File

Create .github/workflows/clanker.yml:

name: Clanker Skills

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  run-skill:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start Clanker Skill
        id: start
        run: |
          RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/skills/readme-generator/run" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{ "input": "Generate a README based on the project structure" }')

          EXEC_ID=$(echo $RESPONSE | jq -r '.executionId')
          echo "exec_id=$EXEC_ID" >> $GITHUB_OUTPUT
          echo "Execution started: $EXEC_ID"

      - name: Wait for Completion
        id: wait
        run: |
          EXEC_ID="${{ steps.start.outputs.exec_id }}"
          TIMEOUT=300
          ELAPSED=0

          while [ $ELAPSED -lt $TIMEOUT ]; do
            STATUS=$(curl -s "https://clanker.net/api/v1/executions/$EXEC_ID/status" \
              -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}")

            CURRENT=$(echo $STATUS | jq -r '.status')
            echo "Status: $CURRENT"

            if [ "$CURRENT" = "completed" ]; then
              ARTIFACT_ID=$(echo $STATUS | jq -r '.artifactId // empty')
              echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
              exit 0
            elif [ "$CURRENT" = "failed" ] || [ "$CURRENT" = "cancelled" ]; then
              echo "Execution $CURRENT"
              exit 1
            fi

            sleep 10
            ELAPSED=$((ELAPSED + 10))
          done

          echo "Timeout exceeded"
          exit 1

      - name: Download Artifact
        if: steps.wait.outputs.artifact_id != ''
        run: |
          curl -o output.md "https://clanker.net/api/v1/artifacts/${{ steps.wait.outputs.artifact_id }}/download" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}"

      - name: Upload to GitHub
        if: steps.wait.outputs.artifact_id != ''
        uses: actions/upload-artifact@v4
        with:
          name: generated-docs
          path: output.md

Use Cases

Auto-Generate README on Release

name: Generate README

on:
  release:
    types: [published]

jobs:
  update-readme:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start README Skill
        id: start
        run: |
          PROJECT_INFO=$(cat << EOF
          Project: ${{ github.repository }}
          Description: ${{ github.event.repository.description }}
          Version: ${{ github.event.release.tag_name }}
          EOF
          )

          RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/skills/readme-generator/run" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg info "$PROJECT_INFO" '{ input: ("Generate a comprehensive README for this project:\n" + $info) }')")

          echo "exec_id=$(echo $RESPONSE | jq -r '.executionId')" >> $GITHUB_OUTPUT

      - name: Wait for Completion
        id: wait
        run: |
          EXEC_ID="${{ steps.start.outputs.exec_id }}"
          TIMEOUT=300
          ELAPSED=0

          while [ $ELAPSED -lt $TIMEOUT ]; do
            STATUS=$(curl -s "https://clanker.net/api/v1/executions/$EXEC_ID/status" \
              -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}")

            CURRENT=$(echo $STATUS | jq -r '.status')

            if [ "$CURRENT" = "completed" ]; then
              ARTIFACT_ID=$(echo $STATUS | jq -r '.artifactId // empty')
              echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
              exit 0
            elif [ "$CURRENT" = "failed" ] || [ "$CURRENT" = "cancelled" ]; then
              exit 1
            fi

            sleep 10
            ELAPSED=$((ELAPSED + 10))
          done
          exit 1

      - name: Update README
        if: steps.wait.outputs.artifact_id != ''
        run: |
          curl -o README.md "https://clanker.net/api/v1/artifacts/${{ steps.wait.outputs.artifact_id }}/download" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}"

          git config user.name github-actions
          git config user.email github-actions@github.com
          git add README.md
          git commit -m "docs: update README for ${{ github.event.release.tag_name }}"
          git push

Code Review on PR

name: AI Code Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get Changed Files
        id: changed
        run: |
          FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | tr '\n' ' ')
          echo "files=$FILES" >> $GITHUB_OUTPUT

      - name: Start Code Review Skill
        id: start
        run: |
          DIFF=$(git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }})

          RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/skills/code-reviewer/run" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg diff "$DIFF" --arg files "${{ steps.changed.outputs.files }}" \
              '{ input: ("Review this PR for bugs, security issues, and best practices:\n\nChanged files: " + $files + "\n\nDiff:\n" + $diff) }')")

          echo "exec_id=$(echo $RESPONSE | jq -r '.executionId')" >> $GITHUB_OUTPUT

      - name: Wait and Get Review
        id: wait
        run: |
          EXEC_ID="${{ steps.start.outputs.exec_id }}"
          TIMEOUT=600
          ELAPSED=0

          while [ $ELAPSED -lt $TIMEOUT ]; do
            STATUS=$(curl -s "https://clanker.net/api/v1/executions/$EXEC_ID/status" \
              -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}")

            CURRENT=$(echo $STATUS | jq -r '.status')

            if [ "$CURRENT" = "completed" ]; then
              ARTIFACT_ID=$(echo $STATUS | jq -r '.artifactId // empty')
              if [ -n "$ARTIFACT_ID" ] && [ "$ARTIFACT_ID" != "null" ]; then
                curl -o review.md "https://clanker.net/api/v1/artifacts/$ARTIFACT_ID/download" \
                  -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}"
              fi
              exit 0
            elif [ "$CURRENT" = "failed" ] || [ "$CURRENT" = "cancelled" ]; then
              exit 1
            fi

            sleep 10
            ELAPSED=$((ELAPSED + 10))
          done
          exit 1

      - name: Post Review Comment
        if: hashFiles('review.md') != ''
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('review.md', 'utf8');

            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## AI Code Review\n\n${review}`
            });

Multi-Step Pipeline

For multi-step pipelines, launch a workflow instead of a single skill. For example, pr-review fetches a pull request and produces a structured summary (it requires the GitHub connector). Workflows return a runId; poll the run for status and step output:

      - name: Run PR Review Workflow
        id: pipeline
        run: |
          RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/workflows/pr-review/launch" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "inputs": {
                "owner": "${{ github.repository_owner }}",
                "repo": "${{ github.event.repository.name }}",
                "pullNumber": ${{ github.event.pull_request.number }}
              }
            }')

          RUN_ID=$(echo $RESPONSE | jq -r '.runId')
          echo "Workflow $RUN_ID started"

Multi-step workflows handle execution slot queueing automatically — if one step needs to wait for another execution to finish, the run suspends and resumes without intervention.

Generate Changelog

name: Generate Changelog

on:
  push:
    tags:
      - 'v*'

jobs:
  changelog:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get Commits Since Last Tag
        id: commits
        run: |
          PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
          if [ -n "$PREV_TAG" ]; then
            COMMITS=$(git log $PREV_TAG..HEAD --pretty=format:"- %s" | head -50)
          else
            COMMITS=$(git log --pretty=format:"- %s" | head -50)
          fi
          echo "commits<<EOF" >> $GITHUB_OUTPUT
          echo "$COMMITS" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Start Changelog Skill
        id: start
        run: |
          RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/skills/changelog-generator/run" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --arg commits "${{ steps.commits.outputs.commits }}" \
              '{ input: ("Generate a changelog entry for version ${{ github.ref_name }} based on these commits:\n" + $commits) }')")

          echo "exec_id=$(echo $RESPONSE | jq -r '.executionId')" >> $GITHUB_OUTPUT

      - name: Wait for Completion
        id: wait
        run: |
          # Same polling pattern as above
          EXEC_ID="${{ steps.start.outputs.exec_id }}"
          TIMEOUT=300
          ELAPSED=0

          while [ $ELAPSED -lt $TIMEOUT ]; do
            STATUS=$(curl -s "https://clanker.net/api/v1/executions/$EXEC_ID/status" \
              -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}")

            CURRENT=$(echo $STATUS | jq -r '.status')

            if [ "$CURRENT" = "completed" ]; then
              ARTIFACT_ID=$(echo $STATUS | jq -r '.artifactId // empty')
              echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
              exit 0
            elif [ "$CURRENT" = "failed" ] || [ "$CURRENT" = "cancelled" ]; then
              exit 1
            fi

            sleep 10
            ELAPSED=$((ELAPSED + 10))
          done
          exit 1

Reusable Action

Create a reusable action for your organization:

.github/actions/clanker-skill/action.yml

name: Run Clanker Skill
description: Start a Clanker skill run and wait for results

inputs:
  api-key:
    description: Clanker API Key
    required: true
  skill:
    description: Skill slug to execute
    required: true
  input:
    description: Input for the skill
    required: true
  timeout:
    description: Timeout in seconds
    default: '300'

outputs:
  execution-id:
    description: ID of the execution
    value: ${{ steps.start.outputs.exec_id }}
  artifact-id:
    description: ID of the generated artifact
    value: ${{ steps.wait.outputs.artifact_id }}
  output-file:
    description: Path to downloaded output
    value: ${{ steps.download.outputs.file }}

runs:
  using: composite
  steps:
    - name: Start Skill
      id: start
      shell: bash
      run: |
        RESPONSE=$(curl -s -X POST "https://clanker.net/api/v1/skills/${{ inputs.skill }}/run" \
          -H "x-api-key: ${{ inputs.api-key }}" \
          -H "Content-Type: application/json" \
          -d "$(jq -n --arg input '${{ inputs.input }}' '{ input: $input }')")

        EXEC_ID=$(echo $RESPONSE | jq -r '.executionId')
        echo "exec_id=$EXEC_ID" >> $GITHUB_OUTPUT

    - name: Wait for Completion
      id: wait
      shell: bash
      run: |
        EXEC_ID="${{ steps.start.outputs.exec_id }}"
        TIMEOUT=${{ inputs.timeout }}
        ELAPSED=0

        while [ $ELAPSED -lt $TIMEOUT ]; do
          STATUS=$(curl -s "https://clanker.net/api/v1/executions/$EXEC_ID/status" \
            -H "x-api-key: ${{ inputs.api-key }}")

          CURRENT=$(echo $STATUS | jq -r '.status')

          if [ "$CURRENT" = "completed" ]; then
            ARTIFACT_ID=$(echo $STATUS | jq -r '.artifactId // empty')
            echo "artifact_id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
            exit 0
          elif [ "$CURRENT" = "failed" ] || [ "$CURRENT" = "cancelled" ]; then
            exit 1
          fi

          sleep 10
          ELAPSED=$((ELAPSED + 10))
        done

        echo "Timeout exceeded"
        exit 1

    - name: Download Output
      id: download
      if: steps.wait.outputs.artifact_id != '' && steps.wait.outputs.artifact_id != 'null'
      shell: bash
      run: |
        curl -o clanker-output.txt "https://clanker.net/api/v1/artifacts/${{ steps.wait.outputs.artifact_id }}/download" \
          -H "x-api-key: ${{ inputs.api-key }}"
        echo "file=clanker-output.txt" >> $GITHUB_OUTPUT

Usage

- uses: ./.github/actions/clanker-skill
  with:
    api-key: ${{ secrets.CLANKER_API_KEY }}
    skill: readme-generator
    input: "Generate README for this project"

Fire-and-forget workflows in CI

To start a workflow without blocking the job on its result, launch it and capture the runId. Poll the run later (or in a separate job) if you need the outcome:

      - name: Start Workflow
        run: |
          RUN=$(curl -s -X POST "https://clanker.net/api/v1/workflows/pr-review/launch" \
            -H "x-api-key: ${{ secrets.CLANKER_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "inputs": {
                "owner": "${{ github.repository_owner }}",
                "repo": "${{ github.event.repository.name }}",
                "pullNumber": ${{ github.event.pull_request.number }}
              }
            }')
          echo "runId=$(echo "$RUN" | jq -r '.runId')" >> "$GITHUB_OUTPUT"

Retrieve results with GET /api/v1/workflows/runs/:runId when you need them.

Best Practices

  1. Use secrets - Never hardcode API keys
  2. Set timeouts - Runs can take time; set appropriate timeouts (default 300s)
  3. Handle failures - Add error handling and notifications
  4. Cache wisely - Don’t re-run skills unnecessarily
  5. Monitor costs - Track Dollarinos usage in CI
  6. Use workflows for chained steps - For pipelines that chain multiple skills, launch a multi-step workflow instead of starting several sequential runs

Next Steps