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
- Clanker account
- API key for authentication
- Skills installed in your Clanker library
Basic Setup
1. Store Credentials
Add your Clanker credentials as GitHub secrets:
- Go to your repository > Settings > Secrets and variables > Actions
- 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
- Use secrets - Never hardcode API keys
- Set timeouts - Runs can take time; set appropriate timeouts (default 300s)
- Handle failures - Add error handling and notifications
- Cache wisely - Don’t re-run skills unnecessarily
- Monitor costs - Track Dollarinos usage in CI
- Use workflows for chained steps - For pipelines that chain multiple skills, launch a multi-step workflow instead of starting several sequential runs
Next Steps
- Workflows Guide - Comprehensive workflow documentation
- Workflows API - Full REST API reference
- Creating Skills - Build custom skills for CI