The user writes tickets in Linear. Cyrus on the always-on machine hands each one to
Claude Code, and the result comes back as a PR, a commit or a deploy, depending on the
repo's rule. Set-up is in workflows/agent-pipeline.md. This skill is the operator's
manual.
| Piece | Value |
|---|---|
| Linear workspace | https://linear.app/<workspace>: one team <KEY>, one project per repo (free plan: 2 teams, unlimited projects) |
| Routing | project name → repo, via projectKeys in ~/.cyrus/config.json |
| Per-repo rule | written in each repo's CLAUDE.md: PR-and-stop / commit to develop / agent deploys |
| Runner | service (launchd/systemd) on port 3456, public https://<agent-host>.<domain> via Cloudflare Tunnel |
| Config | ~/.cyrus/config.json (routing + OAuth tokens) · ~/.cyrus/.env (secrets) |
| Logs | ~/.cyrus/cyrus.log · per ticket ~/.cyrus/logs/<KEY-n>/ |
| Claude | the machine's own claude login; usage counts against the user's plan |
Secrets rule: never cat config.json or .env. Print key names only
(cut -d= -f1 ~/.cyrus/.env), and read tokens straight into a variable (see
references/linear-api.md).
| Want to… | Do |
|---|---|
| Create a ticket and start the agent | issueCreate with projectId + delegateId = the agent's app user (references/linear-api.md). Delegate set on create → the session starts in the same second |
| Hand an existing ticket over | issueUpdate(delegateId). Agents live in delegate, not assignee |
| Check it started | curl localhost:3456/status → busy; grep <KEY-n> ~/.cyrus/cyrus.log; ~/.cyrus/worktrees/<KEY-n> exists. First thought ≈10 s, real work ≈2 min |
| Watch it finish | gh pr list --repo <owner>/<repo> --search <key-n>; summary comment on the ticket |
| Send it back | reply in the ticket thread (same session, same PR) |
| Close a ticket with no PR | a commit on the default branch with Fixes <KEY-n> (verify; not 100% reliable) or set state Done |
| Re-run a ticket that errored | re-delegating does nothing; reply in the thread or file a new ticket |
| Diagnose "didn't start" | references/cyrus.md → Diagnosing |
| API returns 403 | do it in the Linear UI (rename/delete team, team labels) |
gh repo view <owner>/<repo> resolves. Project name = repo name title-cased
(my-app → My App). State the name you chose. projectKeys must match it exactly.cyrus self-add-repo https://github.com/<owner>/<repo>.git (clones and hot-reloads).<KEY> (projectCreate).projectKeys and delete the auto-added routingLabels (labels beat
projects):
bash
jq '.repositories |= map(if .name=="<repo>" then del(.routingLabels) | . + {projectKeys:["<Project Name>"]} else . end)' \
~/.cyrus/config.json > /tmp/c.json && mv /tmp/c.json ~/.cyrus/config.jsonCLAUDE.md with a deploy rule, the first ticket should add one.
It is the agent's only briefing besides the ticket.Title = the change. Description = what to change, then Done when: with 2–4 checkable
lines (including which test or browser flow must still pass), then what not to
touch. Say "deploy" only if the user wants it live. For apps with real users, say "no
schema change" or "docs/tests only" when that is the intent. One small, checkable
change per ticket. Multi-hour phases handed to an unattended agent failed on usage
limits and restarts.
assignee to see who works a ticket. Agents are in delegate.https://<agent-host>/callback while cyrus self-auth-linear waits: it exits and the OAuth redirect 502s.Session error in the log). Run one at a time.