|
Publish Image / publish (push) Successful in 21s
Details
Completed jobs with the same jobId block new jobs from being added. Add removeOnComplete and removeOnFail to all job options so the same task can be re-triggered after completion. 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.