Linear GitHub Worker¶
Moves PEA issues from Approved to Live once every PR linked to them (the issue's
"Diffs") has reached main. QA approves issues by hand, often before the release reaches
main and sometimes after, so the Worker checks all Approved issues on a schedule instead of
listening for a single event.
Schedule¶
Cloudflare cron triggers run in UTC, so the times in wrangler.toml are shifted from IST (UTC+5:30):
| Cron (UTC) | IST |
|---|---|
30 0 * * * |
06:00, every day |
30 12 * * * |
18:00, every day |
When an issue moves¶
For each Approved PEA issue, the Worker takes its attachments that are GitHub PR URLs.
| The issue's PRs | Result |
|---|---|
| None | Stays Approved (no linked PRs) |
| Any PR outside the Scenarix org | Stays Approved: the GitHub App can't read it |
| Any PR open, including drafts | Stays Approved |
| A PR closed without merging | Ignored, because it was abandoned |
A merged PR whose merge commit isn't on main |
Stays Approved until the release lands |
| Only abandoned PRs | Stays Approved (no merged PRs) |
Every merged PR is on main |
Moves to Live |
| A PR or repo can't be read (deleted PR, or a repo the app isn't installed on) | Logged as failed, and the run errors |
A merge commit is on main when GitHub's compare of main against it finds nothing main
lacks (aheadBy === 0). This holds whether the PR merged into staging and shipped with a
release, or merged into main directly. A move to Live is a normal Linear status change, so
feedback-worker still sends the reporter its "resolved" email for Studio Feedback issues.
Requests per run¶
- Linear:
- 1 request for the team's states.
- 1 request per 50 Approved issues.
- 1 request per move.
- GitHub: 2 requests to get an installation token, then 2 GraphQL requests covering every
PR in every repo. One fetches PR states and merge commits; the other compares those commits
to
main. The GraphQL requests split again past 50 PRs or commits.
A typical run stays well under the Free plan's 50-subrequest limit. If a large release ever goes
past it, the moves that were left over are logged as failed. The issues are still Approved, so
the next run moves them.
Configuration¶
| Name | Kind | Purpose |
|---|---|---|
DRY_RUN |
Secret, set by hand | Only "false" moves issues. Unset or any other value logs would_move instead. CI never touches it |
LINEAR_AUTO_PR_API_KEY |
Secret | Synced from the repo secret of the same name: the "Auto PR" app token (read + write). The deploy fails without it |
AUTO_PR_BOT_APP_ID |
Secret | Synced from the repo variable of the same name |
AUTO_PR_BOT_APP_PRIVATE_KEY |
Secret | Synced from the repo secret of the same name |
GITHUB_TOKEN |
Local only | A personal token used in place of the app, so a local run doesn't need the app's key. CI never sets it |
deploy-linear-github-worker.yml runs the tests, syncs the secrets and deploys on every push to
main that touches this directory or the shared helpers it imports from worker/src/lib.
Turning moves on and off¶
DRY_RUN is a secret that CI doesn't sync, so a deploy never resets it, and a change applies
from the next run without a redeploy. A fresh deploy has it unset, which means dry run.
- Dashboard: Workers & Pages →
linear-github-worker→ Settings → Variables and Secrets →DRY_RUN. Set it tofalseto move issues, ortrueto only log. - CLI:
cd linear-github-worker printf false | npx wrangler secret put DRY_RUN # start moving issues printf true | npx wrangler secret put DRY_RUN # back to logging only
The sweep_complete log line's dryRun field shows which mode each run used.
Logs¶
Each run writes one JSON line per issue, then a summary line. View them in Workers Logs.
event |
Fields |
|---|---|
moved / would_move |
issue, title, url, pulls |
skipped |
issue, reason |
failed |
issue, url, error |
sweep_complete |
cron, dryRun, checked, moved (or wouldMove), failed, skipped |
Local run¶
cd linear-github-worker
npm ci
npm test
cp .dev.vars.example .dev.vars # then fill in the secrets
npm run dev # wrangler dev --test-scheduled
curl "http://localhost:8787/cdn-cgi/local/scheduled?cron=30+0+*+*+*"
The run's log lines appear in the npm run dev terminal. Keep DRY_RUN=true in .dev.vars:
a real move from a local run emails the reporter the same way a deployed one does.
If you don't have the app's private key, set GITHUB_TOKEN instead. Use a fine-grained token
owned by Scenarix, with access to the repos and read-only Contents and Pull requests.