A one-time setup for production. Do the steps in this order; each one depends on the one
before it. Secrets go in 1Password under “Sally (sally.linchpin.com)” and are
set on the Worker with npx wrangler secret put <NAME> --name sally.
1. Cloudflare resources
For a fresh account:
npx wrangler d1 create sally # put the database_id in wrangler.jsonc
npx wrangler queues create sally
npx wrangler queues create sally-dlq
Then create a key for the AI gateway:
- Issue a
sallytenant key on ai.linchpin.com:npm run key:issueinlinchpin/ai-gateway. - Store it with
wrangler secret put AI_GATEWAY_KEY. TOKEN_ENC_KEYwas generated and set at the first deploy (see step 3). It exists only in the Worker. If it is ever
rotated, stored ClickUp connections stop decrypting, and everyone runs/sally connectagain.
2. Cloudflare Access
Create a self-hosted Access application named Sally, matching the api, status and cron admins. Creating it through the API failed with error 1010 (the agent’s Cloudflare connection lacks Access write), so it is made in the dashboard: Zero Trust › Access › Applications › Add an application › Self-hosted.
- Path:
sally.linchpin.com/admin. Access matches everything below it, including/admin/api/*. - Policy: the reusable Linchpin Security Policy, Google as the identity provider,
instant auth on, and a 24-hour session. Linchpin staff only. Do not cover the whole hostname:/slack/*and/clickup/*must stay reachable by Slack and ClickUp. - Copy the application’s AUD tag into
CF_ACCESS_AUDinwrangler.jsonc.
Until CF_ACCESS_AUD is set, the admin answers 503: it fails closed.
3. First deploy
Done on 2026-10-12, from the Phase 2 branch. This step created the sally Worker, the sally.linchpin.com custom
domain, its cron trigger, and the queue producer and consumer.
Migrations come first:
npx wrangler d1 migrations apply sally --remote
Why the first deploy needs a secrets file. secrets.required in wrangler.jsonc makes a deploy fail until every
secret is set. A Worker that does not exist yet cannot have secrets set with wrangler secret put. So the first deploy
passes them in a file:
- a generated
TOKEN_ENC_KEY, - empty values for the Slack, ClickUp and AI gateway secrets.
Sally treats an empty secret as not configured: Slack requests get a 503, and /sally connect says ClickUp isn’t set
up. Delete the file afterwards.
npx wrangler deploy --secrets-file /path/outside/the/repo/first-deploy.json
Then seed the four people. Everyone starts inactive and in dry run.
npx wrangler d1 execute sally --remote --file scripts/seed-people.sql
Later deploys go through release-please and deploy.yml: see Releasing.
4. Slack app
Follow slack/README.md:
- Update the old Sally app’s manifest, or create a new app from
slack/manifest.json. - Install it.
- Set
SLACK_BOT_TOKENandSLACK_SIGNING_SECRET.
Slack checks the Events URL when it is saved, so the Worker must already be deployed.
The WAF has to let Slack’s button clicks through. The linchpin.com zone runs the OWASP Core Ruleset on every
request, with 949110: Inbound Anomaly Score Exceeded set to a managed challenge.
- Why clicks are affected: a click’s body is URL-encoded Block Kit JSON full of
<url|text>links and quotes, so
it scores as an attack. - What Slack sees: the challenge, which it cannot solve, so the button shows a ⚠️ and Sally never sees the click.
Events and slash commands usually score low enough to pass, which makes this easy to miss. - The fix: a managed-rules exception, “Skip OWASP on Slack message” in linchpin.com › Security › WAF ›
Managed rules. It skips rule 949110 only, for:(http.host eq "sally.linchpin.com" and http.request.method eq "POST" and (starts_with(http.request.uri.path, "/slack/") or http.request.uri.path eq "/clickup/webhook")) - Why skipping it is safe: Sally verifies every one of those requests itself, with Slack’s signature or ClickUp’s
X-Signature. - How to tell if it’s happening: Security › Events shows
Slackbot 1.0requests to/slack/interactionswith
the action Managed Challenge.
5. ClickUp OAuth app
In ClickUp: Settings › Apps › Create new app, named “Sally”.
- Set the redirect URL to
sally.linchpin.com. - Set
CLICKUP_CLIENT_IDandCLICKUP_CLIENT_SECRET.
6. Connect, then register the webhook
- Each person runs
/sally connectin Slack. - Aaron opens sally.linchpin.com/admin › Health › Register webhook.
Registering the webhook uses Aaron’s own ClickUp connection. ClickUp returns its signing secret once; Sally stores it encrypted in D1.
7. Switch people on
Turn people on from the admin, one at a time, in this order: Aaron, then Julia, then Francisco and Brian. Turn off dry run only after each person’s draft has been checked against what they actually did.
Releasing
Production runs what was released, nothing else. The rewrite has been on main since v2.0.0 (19 Oct 2026).
- Merge work to
mainwith conventional commits scoped to the ClickUp task (feat(LINCHPIN-5818): …). - release-please keeps a release PR open, “chore(main): release x.y.z”, with the version bump and the
CHANGELOG.mdentries for everything merged since the last release. It updates the PR on every merge. - Merging the release PR tags
vx.y.zand publishes the GitHub release. - The release runs
deploy.yml:npm run check, then the D1 migrations, then the deploy. Migrations go first, so
new code never runs against an old schema.
Versions: every release bumps the patch (always-bump-patch), whatever the commits. The first Worker release was
2.0.0, set once by a Release-As: 2.0.0 commit footer; the PHP plugin stopped at 1.0.7.
Deploying by hand: run Deploy to Cloudflare from the Actions tab on main. It runs the same steps. Don’t
wrangler deploy from a laptop or a branch, or production stops matching a release.
What the workflows need:
GH_BOT_TOKEN, for release-please. A release made with the defaultGITHUB_TOKENdoesn’t start other workflows, sodeploy.ymlwould never run.CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID, for the deploy. The token is an account API token
(Manage account › Account API tokens) with two policies:- on the Linchpin account: Workers › Editor, D1 › Editor for the migrations, and Queues › Editor for
the queue consumer, - on the linchpin.com zone: Workers Routes › Write, for the
sally.linchpin.comcustom domain.
- on the Linchpin account: Workers › Editor, D1 › Editor for the migrations, and Queues › Editor for