Run Cyrus Community Edition (Claude Code-powered Linear agent) on Cloudflare's edge infrastructure using the Sandbox SDK.
Inspired by Moltworker.
Instead of running Cyrus Community Edition on a local Mac mini or VPS:
- No hardware required - Runs in Cloudflare Sandbox containers
- Always on - Auto-bootstraps on first webhook, sleeps when idle to save compute
- Global edge - Low latency webhook processing worldwide
- Persistent storage - R2 backup of config, tokens, and repo URLs
- Secure - Webhook signature verification protects endpoints
- PHI-conscious - Minimizes logging of Linear issue content (see CLAUDE.md)
- Workers Paid plan ($5/month) - Required for Sandbox
- Anthropic API key - For Claude Code
- GitHub PAT - For PR creation
- Linear workspace with admin access
Or manually:
# Clone and install
git clone https://github.com/brianleach/cyrusworker.git
cd cyrusworker
npm install
# Deploy (requires Docker running)
npm run deployAfter deployment, note your worker URL (e.g., https://cyrusworker.your-subdomain.workers.dev).
Cyrus uses Linear's OAuth Applications with Agent Session Events - not standard webhooks.
- Go to Linear Settings → API → OAuth Applications
- Click Create new OAuth Application
- Fill in:
- Name:
Cyrus(this is how it appears in Linear) - Callback URL:
https://your-worker.workers.dev/callback
- Name:
- Enable these toggles:
- ✅ Client credentials
- ✅ Webhooks
- Configure webhook settings:
- Webhook URL:
https://your-worker.workers.dev/webhook - App events: ✅ Agent session events (required - makes Cyrus appear as an agent)
- Webhook URL:
- Save and copy these credentials:
- Client ID
- Client Secret (only shown once!)
- Webhook Signing Secret (from webhook settings)
# Linear OAuth credentials
npx wrangler secret put LINEAR_CLIENT_ID
npx wrangler secret put LINEAR_CLIENT_SECRET
npx wrangler secret put LINEAR_WEBHOOK_SECRET
# Claude Code
npx wrangler secret put ANTHROPIC_API_KEY
# GitHub (for PR creation)
npx wrangler secret put GH_TOKEN
npx wrangler secret put GIT_USER_NAME
npx wrangler secret put GIT_USER_EMAIL
# Admin UI protection (generate a random token)
export GATEWAY_TOKEN=$(openssl rand -hex 32)
echo "Save this token: $GATEWAY_TOKEN"
echo "$GATEWAY_TOKEN" | npx wrangler secret put GATEWAY_TOKEN
# Treat this token like a password - anyone with it can access your Admin UIVisit the authorization URL (replace with your values):
https://linear.app/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://YOUR_WORKER.workers.dev/callback&response_type=code&scope=write,app:assignable,app:mentionable&actor=app
You should see "Authorization Complete!" with your organization name.
Open the Admin UI at https://your-worker.workers.dev/_admin/?token=YOUR_GATEWAY_TOKEN and use the "Add Repository" form, or via API:
curl -X POST "https://your-worker.workers.dev/api/add-repo?token=YOUR_GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://github.com/your-org/your-repo.git"}'In Linear, open any issue and click Delegate to... → Cyrus. Cyrus will process the issue using Claude Code.
- User delegates an issue to Cyrus or @mentions it in a comment
- Linear sends an
AgentSessionEventwebhook to your worker - Worker checks if Cyrus is running; if not, auto-bootstraps (restores config from R2, clones repos, starts Cyrus)
- Worker forwards the webhook to Cyrus EdgeWorker (port 3456)
- Cyrus processes the issue using Claude Code
- Results are posted back to Linear
Note: Cyrus appears as a delegatable agent in Linear's "Delegate to..." menu, not as a regular user in the assignee list.
The Admin UI is protected by the GATEWAY_TOKEN set in Step 2. Access it at:
https://your-worker.workers.dev/_admin/?token=YOUR_GATEWAY_TOKEN
Features:
- Cyrus Status - View status (idle/busy/offline), version, repo count
- Repositories - List configured repos, add new repos
- Logs - View Cyrus EdgeWorker logs
- Storage - Save/restore config to R2
- Bootstrap - Manually trigger bootstrap
- Execute - Run commands in the sandbox
All /api/* endpoints require the GATEWAY_TOKEN query parameter (e.g., /api/status?token=YOUR_TOKEN).
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check |
/webhook |
POST | Linear AgentSessionEvent receiver (auto-bootstraps) |
/callback |
GET | Linear OAuth callback |
/api/bootstrap |
POST | Full bootstrap: restore, clone repos, start Cyrus |
/api/status |
GET | Sandbox process and disk status |
/api/config |
GET | Current Cyrus config |
/api/init |
POST | Initialize Cyrus .env from Worker secrets |
/api/start |
POST | Start Cyrus EdgeWorker |
/api/add-repo |
POST | Add repository ({url, workspace?}) |
/api/restore |
POST | Restore config from R2 |
/api/save |
POST | Save config to R2 |
/api/exec |
POST | Execute command in sandbox |
/api/backup |
POST | Backup full ~/.cyrus to R2 |
/_admin/ |
GET | Admin UI |
| Secret | Required | Description |
|---|---|---|
LINEAR_CLIENT_ID |
Yes | Linear OAuth Application client ID |
LINEAR_CLIENT_SECRET |
Yes | Linear OAuth Application client secret |
LINEAR_WEBHOOK_SECRET |
Yes | Linear OAuth Application webhook signing secret |
ANTHROPIC_API_KEY |
Yes | Claude API key for Claude Code |
GH_TOKEN |
Yes | GitHub PAT for PR creation |
GIT_USER_NAME |
Yes | Git commit author name |
GIT_USER_EMAIL |
Yes | Git commit author email |
GATEWAY_TOKEN |
Recommended | Token to protect Admin UI access |
GIT_SSH_PRIVATE_KEY |
No | SSH private key for private repos |
From your Linear OAuth Application (see Step 1).
- Go to Anthropic Console
- Navigate to API Keys
- Click Create Key
- Copy the key (starts with
sk-ant-)
- Go to GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Click Generate new token
- Under Repository access, select the repos Cyrus should access
- Under Permissions → Repository permissions, enable:
- Contents: Read and write
- Pull requests: Read and write
- Metadata: Read-only
- Click Generate token
Used for git commits:
npx wrangler secret put GIT_USER_NAME # e.g., "Cyrus"
npx wrangler secret put GIT_USER_EMAIL # e.g., "cyrus@your-domain.com"The GH_TOKEN handles HTTPS authentication automatically.
# Generate a deploy key
ssh-keygen -t ed25519 -C "cyrus@your-domain.com" -f cyrus-key -N ""
# Add cyrus-key.pub as a deploy key in your GitHub repo
# Then set the private key
npx wrangler secret put GIT_SSH_PRIVATE_KEY < cyrus-key# Create .dev.vars with secrets
cp .dev.vars.example .dev.vars
# Start dev server (requires Docker)
npm run devSee CLAUDE.md for detailed architecture documentation.
- Verify Agent session events is enabled in your OAuth Application
- Ensure you completed the OAuth authorization flow
- Check that the webhook URL is correct and responding
This usually means the OAuth token in Cyrus's config is stale. To fix:
- Open Admin UI and click Bootstrap - this syncs the fresh token from storage to Cyrus
- If that doesn't work, click Reauthorize to get a completely new token, then Bootstrap
You can verify the token is working by running this in Execute:
cat /root/.cyrus/config.json | grep linearTokenThen test it:
curl -s -H "Authorization: Bearer YOUR_TOKEN_HERE" https://api.linear.app/graphql \
-H "Content-Type: application/json" -d '{"query":"{ viewer { id } }"}'- Check webhook URL matches your worker:
https://your-worker.workers.dev/webhook - Verify
LINEAR_WEBHOOK_SECRETmatches the signing secret in Linear - Check worker logs:
npm run tail
- Ensure
LINEAR_CLIENT_IDandLINEAR_CLIENT_SECRETare set correctly - Check the callback URL matches:
https://your-worker.workers.dev/callback
- Verify
GH_TOKENhas access to the repository - Check the repository URL is correct (HTTPS format)
- View logs in Admin UI for detailed error messages
MIT
