GitHub Integration Setup Guide
This guide walks you through connecting Lathe Studio to GitHub for CI/CD pipeline integration and issue tracking.
Prerequisites#
- GitHub account (personal or organization)
- GitHub repository with Actions workflows
- Project admin access in Lathe Studio
Overview#
The GitHub integration enables:
- CI/CD Webhooks: Automatically create builds when GitHub Actions workflows complete
- Issue Sync: Link test failures to GitHub Issues
- Pull Request Status: Display test results directly in PRs
Connecting GitHub#
Step 1: Create an API Key
- Go to Settings > Organization > API Keys
- Click Create API Key
- Give it a name (e.g., "GitHub Actions")
- Select scopes:
builds:read- View buildsbuilds:create- Create new builds
- Copy the generated key (shown only once)
Step 2: Configure GitHub Webhook
- In GitHub, go to your repository
- Navigate to Settings > Webhooks
- Click Add webhook
- Configure:
- Payload URL:
https://lathestudio.dev/api/webhooks/github - Content type:
application/json - Secret: (optional) Add a webhook secret for signature verification
- SSL verification: Enable
- Payload URL:
- Events: Select "Workflow runs" or "Send me everything"
- Click Add webhook
Step 3: Add API Key to Headers
In the webhook configuration, add a custom header:
- Name:
Authorization - Value:
Bearer <your-api-key>
Alternatively, use the X-Pt-Org-Id header with your organization ID.
Step 4: Test the Integration
- Trigger a workflow run in GitHub Actions
- Go to Releases page in Lathe Studio
- You should see the build in the Build Queue section
- Assign it to a release
Authentication Methods#
Method 1: API Key (Recommended)
Include the API key in the Authorization header:
Authorization: Bearer pt_live_xxxxxxxxxxxxxxxx
Benefits:
- Rate limiting per key
- Usage tracking
- Scoped permissions
Method 2: Organization ID
Include your organization ID in a custom header:
X-Pt-Org-Id: your-org-uuid
Note: This method has basic rate limiting and is less secure. Use API keys for production.
How It Works#
- Workflow Completes → GitHub sends webhook to
/api/webhooks/github - Authentication → Webhook verifies API key or org ID
- Project Matching → System tries to match repo URL to a project
- Queue or Create:
- If project + active release found → Build created directly
- If project found but no release → Queued for release assignment
- If no project match → Queued for project + release assignment
- Assignment → User assigns queued builds via Releases page
Build Queue UI#
The Build Queue appears on the Releases page when there are pending builds:
- Shows build name, source (GitHub), project, commit/branch
- Assign to Release: Select an active release for the project
- Select Project: Navigate to projects if project not auto-detected
- Reject: Remove build from queue (no build created)
GitHub Actions Example#
Add this step to your existing .github/workflows/test.yml after your test runner finishes. The if: always() ensures results are sent even if tests fail.
name: Test and Report
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Tests
run: npm test -- --reporter=json --outputFile=test-results.json
- name: Upload Results to Lathe Studio
if: always()
env:
LATHE_API_KEY: ${{ secrets.LATHE_STUDIO_API_KEY }}
run: |
# Parse your test framework's JSON output into the Lathe Studio format.
# Below is an example for Jest — adapt the jq filter to your framework.
RESULTS=$(jq -c '
[.testResults[] | .assertionResults[] | select(.status != "pending") | {
test_identifier: .ancestorTitles + [.title] | join(" > "),
name: .title,
status: (if .status == "passed" then "passed" elif .status == "failed" then "failed" else "skipped" end),
duration_ms: .duration,
error: (if .failureMessages | length > 0 then .failureMessages[0] else null end)
}]
' test-results.json)
curl -X POST https://perpetualtest.dev/api/v1/results/ingest \
-H "Authorization: Bearer $LATHE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"build\": {
\"identifier\": \"${{ github.ref_name }}-${{ github.run_number }}\",
\"branch\": \"${{ github.ref_name }}\",
\"commit_sha\": \"${{ github.sha }}\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}\"
},
\"source\": {
\"pipeline\": \"github_actions\",
\"run_id\": \"${{ github.run_id }}\",
\"run_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
},
\"results\": $RESULTS
}"
What this does:
- Runs your tests and outputs JSON (Jest example shown)
- Parses the JSON into Lathe Studio's
IngestResultformat - Sends everything to
POST /api/v1/results/ingest - Lathe Studio automatically:
- Creates a Build (or re-uses an existing one by
identifier) - Creates a completed Test Run linked to that Build
- Matches results to existing Test Cases by
test_identifier - Auto-creates new Test Cases for unknown identifiers
- Creates a Build (or re-uses an existing one by
Response:
{
"build_id": "uuid",
"build_display_id": "main-1042",
"test_run_id": "uuid",
"test_run_display_id": "RUN-MOB-0123",
"test_run_url": "/projects/.../runs/...",
"test_cases_created": 3,
"test_cases_linked": 47,
"results_imported": 50
}
Adapting to other test frameworks:
| Framework | Approach |
|---|---|
| Jest | Use --json --outputFile= then jq to map fields |
| Pytest | Use pytest-json-report plugin, map nodeid → test_identifier |
| JUnit XML | Convert XML to JSON with xq or a Python script first |
| Cypress | Use cypress-multi-reporters with mocha-junit-reporter, then convert |
| Playwright | Use blob reporter + npx playwright merge-reports --reporter=json |
The only requirement is that each result has a stable test_identifier string. Use your existing display codes (TC-MOB-0042) or any unique ID — Lathe Studio will match them automatically.
Troubleshooting#
Builds not appearing
- Check webhook delivery in GitHub (Settings > Webhooks > Recent Deliveries)
- Verify API key is not expired or revoked
- Ensure the workflow run event is being sent
- Check that the repository URL matches your project settings
Wrong project assigned
The system matches by:
- Repository URL (exact match)
- Repository name (partial match)
You can manually assign to the correct project from the queue.
Rate limiting
API keys are limited to 60 requests per minute by default. Contact support to increase limits.
Webhook signature verification fails
If using webhook secrets:
- Verify the secret is correctly configured in both GitHub and Lathe Studio
- Check that the signature header is being sent
- Ensure the payload hasn't been modified
Security#
- API keys are hashed using SHA-256 before storage
- Only the first 12 characters are stored for identification
- Keys can be revoked at any time
- Webhook signatures (HMAC-SHA256) are supported for verification
- All webhook communications use HTTPS
Unlinking GitHub#
To disconnect:
- Go to your GitHub repository
- Navigate to Settings > Webhooks
- Find the Lathe Studio webhook
- Click Delete
Note: Previously created builds remain in Lathe Studio. New builds will not be created until reconnected.
Advanced Configuration#
Custom Build Names
Override the default build name by adding metadata to your workflow:
- name: Set Build Name
run: echo "PT_BUILD_NAME=Release ${{ github.ref_name }}" >> $GITHUB_ENV
Branch Filtering
Configure which branches trigger builds in Project Settings > CI/CD:
- Include:
main,develop,release/* - Exclude:
dependabot/*,hotfix/*
For additional help, contact support.