Linear OpenCode webhook + Railway production (web)

Date: 2026-04-15

This runbook ties together the Brainforge Platform Linear OAuth app, the Forge host platform.brainforge.ai (no www), and Railway service web (production).

Preconditions

  • 1Password CLI: op signin (vault: Brainforge Platform Team, item: Brainforge Platform Linear App).
  • Railway CLI: railway login and access to project brainforge-platform.
  • Linear workspace admin for installing / re-authorizing the app.

1) Linear — webhook URL (must be public DNS)

In Linear → Settings → API → Applications → Brainforge Platform → Webhooks:

  • Webhook URL must be exactly:

https://platform.brainforge.ai/api/brainforge/linear/opencode-webhook

Do not use www.platform.brainforge.ai (NXDOMAIN unless DNS is added).

  • Enable webhook categories needed for agents (at minimum Agent session / equivalent wording in Linear’s UI).

Copy the Webhook signing secret shown in Linear into 1Password (same secure note is fine: Signing Secret: / Webhook Secret: lines).

2) Railway — which service

Production Forge runs on Railway service web (custom domain platform.brainforge.ai).

From repo:

cd apps/platform
railway link -e production            # if needed
railway service link web
railway status

You should see Service: web.

Canonical URL for Linear OAuth (redirect_uri)

Linear returns Invalid redirect_uri if the authorize request uses a callback origin that is not exactly listed under the OAuth app’s Callback URLs.

Set on service web (production):

railway variable set -s web -e production NEXT_PUBLIC_BASE_URL=https://platform.brainforge.ai

The Forge OAuth routes prefer NEXT_PUBLIC_BASE_URL over the request Host, so opening the flow via www (or another alias) still sends redirect_uri=https://platform.brainforge.ai/api/brainforge/linear/oauth/callback, matching the usual Linear registration.

3) Railway — set the signing secret from 1Password

Automated (recommended):

cd apps/platform
python3 scripts/sync-linear-opencode-webhook-secret-to-railway.py

Manual alternative (avoid pasting secrets into chat logs):

cd apps/platform
railway service link web
op read "op://Brainforge Platform Team/Brainforge Platform Linear App/notesPlain" |   # do not commit command history with secrets
railway variable set -s web -e production BRAINFORGE_PLATFORM_LINEAR_APP_OPENCODE_WEBHOOK_SECRET --stdin

After changing variables, redeploy web if Railway did not auto-deploy.

4) Forge — re-authorize OAuth (agent scopes)

A workspace admin opens (no www):

https://platform.brainforge.ai/api/brainforge/linear/oauth/authorize

Complete consent so Linear grants actor=app and app:assignable / app:mentionable (see merged platform authorize route).

Auth middleware: these GET routes must be reachable without a Forge Supabase session (admins may not be logged into The Forge). The platform middleware allows unauthenticated access to /api/brainforge/linear/oauth/authorize and /api/brainforge/linear/oauth/callback specifically for this flow.

5) Smoke check

  • Delegate an issue to the Brainforge Platform app in Linear.
  • railway logs -s web -e production and look for [Linear OpenCode Webhook] / insert errors.

References

  • Linear Agents + scopes: https://linear.app/developers/agents#actor-and-scopes
  • Platform route: apps/platform/src/app/api/brainforge/linear/opencode-webhook/route.ts
  • Env: BRAINFORGE_PLATFORM_LINEAR_APP_OPENCODE_WEBHOOK_SECRET (see apps/platform/src/lib/linearOpenCodeWebhookSecret.ts)