|
Publish Image / publish (push) Successful in 22s
Details
PR comments fire as issue_comment webhooks in Gitea. Previously the handler only re-triggered the analyst for regular issues. Now it detects PR comments via the pull_request field and queues a test workspace using the review bot account. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .gitea/workflows | ||
| coder | ||
| k8s | ||
| src | ||
| .gitignore | ||
| Dockerfile | ||
| README.md | ||
| package-lock.json | ||
| package.json | ||
| tsconfig.json | ||
README.md
SDLC Orchestrator
Lightweight webhook-driven orchestrator for the Gitea + Coder + Claude Code SDLC pipeline. Receives Gitea webhooks, creates ephemeral Coder workspaces running Claude Code, and cleans up when done.
Architecture
Gitea Webhook → Orchestrator → Coder API (create workspace)
→ Gitea API (post status comment)
Workspace runs Claude Code → reads issue → does work → posts results to Gitea
→ POST /webhook/task-complete/:name (callback)
Orchestrator receives callback → deletes workspace via Coder API
Adding a New Project
To add a new Gitea repo to the SDLC pipeline:
1. Add Claude Code slash commands to the repo
Create .claude/commands/ in the repo with command files for each SDLC stage:
.claude/commands/
├── analyse.md # Business analyst — requirements & acceptance criteria
├── architect.md # Systems architect — design doc & spec
├── develop.md # Developer — implement & create PR
├── review-code.md # Reviewer — approve or request changes
├── test.md # QA — test report & additional tests
├── fix-bug.md # Bug fix — skip analyse/architect
├── rework-pr.md # Address review feedback
├── release.md # Tag RC or production release
├── devops.md # Migration & deployment
└── maintenance.md # Weekly maintenance tasks
Each file uses this format:
---
description: What this command does
allowed-tools: Read, Bash, Glob, Grep
---
You are a **Role Name**. Your job is to...
## Process
1. **Read the issue**:
```bash
curl -s "https://gitea.samson.media/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}/issues/$ARGUMENTS" \
-H "Authorization: token ${GITEA_TOKEN}" | jq '{title, body, labels: [.labels[].name]}'
-
Do your work...
-
Post a comment:
curl -X POST "https://gitea.samson.media/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}/issues/$ARGUMENTS/comments" \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d '{"body": "..."}'
`$ARGUMENTS` is replaced with the issue/PR number at runtime.
### 2. Add bot users as collaborators
Add both `claude-dev` and `claude-review` as **direct collaborators** with **Write** access on the repo. Org membership alone is not sufficient.
### 3. Configure Gitea webhooks
In the repo's **Settings → Webhooks**, create these webhooks:
| # | URL | Events |
|---|-----|--------|
| 1 | `https://sdlc.samson.media/webhook/gitea-issue-triage` | Issues: `opened` |
| 2 | `https://sdlc.samson.media/webhook/gitea-issue-label` | Issues: `labeled` |
| 3 | `https://sdlc.samson.media/webhook/gitea-issue-comment` | Issue Comment: `created`, `edited` |
| 4 | `https://sdlc.samson.media/webhook/gitea-pr-review` | Pull Request: `opened`, `synchronized` |
| 5 | `https://sdlc.samson.media/webhook/gitea-pr-review-rework` | Pull Request Review: `submitted` |
| 6 | `https://sdlc.samson.media/webhook/gitea-release` | Issues: `opened`, `labeled` |
All webhooks use:
- Content type: `application/json`
- Method: `POST`
### 4. (Optional) Add to maintenance cron
To include the repo in weekly maintenance, add it to the `MAINTENANCE_REPOS` env var:
MAINTENANCE_REPOS=org1/repo1=https://gitea.samson.media/org1/repo1.git,org2/repo2=https://gitea.samson.media/org2/repo2.git
### 5. Test it
Create an issue in the repo. The orchestrator will:
1. Receive the `issues/opened` webhook
2. Create a Coder workspace running `/analyse`
3. Claude reads the issue, posts clarifying questions as a comment
4. Workspace is deleted automatically when done
## SDLC Flow
| Stage | Trigger | Task Type | Bot Account |
|-------|---------|-----------|-------------|
| Analyse | Issue opened / comment | `analyse` | claude-dev |
| Architect | Label: `ready-for-architecture` | `architect` | claude-dev |
| Develop | Label: `ready-for-development` | `develop` | claude-dev |
| Review | PR opened/updated | `review-code` | claude-review |
| Rework | Review: changes requested | `rework-pr` | claude-dev |
| Test | Review: approved | `test` | claude-review |
| Deploy | Label: `ready-for-deployment` | `devops` | claude-dev |
| Fix Bug | Issue opened with `bug` label | `fix-bug` | claude-dev |
## Infrastructure Setup
### Prerequisites
- k3s cluster with Traefik ingress and cert-manager
- Coder instance (e.g. `coder.samson.media`)
- Gitea instance (e.g. `gitea.samson.media`)
- Docker registry (e.g. `registry.samson.media`)
- Claude Code Max subscription (for OAuth token)
### Deploy the Orchestrator
1. **Build the workspace image** (pre-installed Node.js, Claude Code, Playwright deps):
```bash
docker build -f coder/workspace.Dockerfile -t registry.samson.media/coder-workspace:latest .
docker push registry.samson.media/coder-workspace:latest
-
Push the Coder template:
coder templates push cloudflare-worker --directory coder/cloudflare-worker --yesNote the template ID from the Coder dashboard.
-
Create Gitea bot accounts:
claude-dev— used for most stages (analyse, develop, etc.)claude-review— used for review and test stages- Generate API tokens for each
-
Generate Claude Code OAuth token (uses Max subscription instead of API credits):
claude setup-tokenThis opens a browser for auth and outputs a long-lived token (
sk-ant-oat01-...). -
Create k8s secrets:
kubectl create namespace sdlc-orchestrator kubectl create secret generic orchestrator-secrets -n sdlc-orchestrator \ --from-literal=CODER_URL=https://coder.samson.media \ --from-literal=CODER_TOKEN=<coder-session-token> \ --from-literal=CODER_TEMPLATE_ID=<template-id> \ --from-literal=GITEA_DEV_TOKEN=<claude-dev-token> \ --from-literal=GITEA_REVIEW_TOKEN=<claude-review-token> \ --from-literal=CLAUDE_OAUTH_TOKEN=<oauth-token-from-step-4> \ --from-literal=CALLBACK_URL=https://sdlc.samson.media \ --from-literal=GITEA_URL=https://gitea.samson.media \ --from-literal=BOT_DEV_USERNAME=claude-dev \ --from-literal=BOT_REVIEW_USERNAME=claude-review -
Deploy:
# Tag to trigger CI build git tag -a v1.0.0 -m "Initial release" git push origin main --tags # Or deploy manually docker build -t registry.samson.media/sdlc-orchestrator:latest . docker push registry.samson.media/sdlc-orchestrator:latest kubectl apply -f k8s/deployment.yaml
Environment Variables
| Variable | Required | Description |
|---|---|---|
PORT |
No | HTTP port (default: 3000) |
CODER_URL |
Yes | Coder API base URL |
CODER_TOKEN |
Yes | Coder session token |
CODER_TEMPLATE_ID |
Yes | Coder workspace template ID |
GITEA_DEV_TOKEN |
Yes | Gitea API token for claude-dev |
GITEA_REVIEW_TOKEN |
Yes | Gitea API token for claude-review |
CLAUDE_OAUTH_TOKEN |
Yes | Claude Code Max OAuth token |
CALLBACK_URL |
Yes | Public URL of this service (e.g. https://sdlc.samson.media) |
GITEA_URL |
No | Gitea base URL (default: https://gitea.samson.media) |
BOT_DEV_USERNAME |
No | Dev bot username (default: claude-dev) |
BOT_REVIEW_USERNAME |
No | Review bot username (default: claude-review) |
QUEUE_CONCURRENCY |
No | Max concurrent workspaces (default: 2) |
MAINTENANCE_REPOS |
No | Comma-separated org/repo=clone_url for weekly maintenance |
Important: Do NOT set
ANTHROPIC_API_KEYin the workspace — it takes priority over the OAuth token and will use pay-per-use credits instead of your Max subscription.
Webhook Endpoints
| Endpoint | Purpose |
|---|---|
GET /health |
Health check |
GET /queue |
Queue status (pending, running, active workspaces) |
POST /webhook/gitea-issue-triage |
Route new issues to analyse or fix-bug |
POST /webhook/gitea-issue-label |
Stage transitions via labels |
POST /webhook/gitea-issue-comment |
Re-trigger analyst on new/edited comments |
POST /webhook/gitea-pr-review |
Trigger code review on PR open/update |
POST /webhook/gitea-pr-review-rework |
Route review outcomes to rework or test |
POST /webhook/gitea-release |
Handle release workflow |
POST /webhook/task-complete/:name |
Callback from workspaces when done |
How Workspaces Work
Workspaces are ephemeral (emptyDir, no persistent storage):
- Orchestrator creates workspace via Coder API with parameters (issue number, task type, tokens, callback URL)
- Startup script clones the repo and checks out
develop - For heavy stages (develop, test, review, rework, fix-bug, devops): installs deps, builds frontend, installs Playwright
- For lightweight stages (analyse, architect, release, maintenance): skips build
- Reads
.claude/commands/<task_type>.md, strips YAML frontmatter, substitutes$ARGUMENTSwith issue number - Runs
claude -p --dangerously-skip-permissions --verbose "<prompt>" - On completion (success or failure via ERR/EXIT trap): POSTs to callback URL
- Orchestrator receives callback, deletes workspace via Coder API
Resilience
- Deduplication: Tasks keyed by
{taskType}-{repo}-{issue}. Duplicate webhooks are dropped. - Startup reconciliation: On boot, queries Coder for running workspaces and re-adopts them into the queue.
- 409 handling: If workspace already exists — adopts if running, deletes and retries if stopped/failed.
- Callback trap: Startup script uses
trapto always fire the callback, even on clone failure or other errors. - TTL safety net: Coder template has 1-hour auto-stop as a backstop for missed callbacks.
CI/CD
| Workflow | Trigger | Output |
|---|---|---|
publish.yml |
v* tags |
registry.samson.media/sdlc-orchestrator:<version> + :latest |
publish-workspace.yml |
Changes to coder/workspace.Dockerfile on main |
registry.samson.media/coder-workspace:latest |
Fleet GitOps (in samson-media/devops repo) watches registry.samson.media/sdlc-orchestrator:latest for deployment.