agents/skills/multiworker-workflow/SKILL.md
Repo-root commands, typegen and typecheck cadence, lint, deploy, adding packages with bun, and Alchemy app layout. Use at the start of a task, before PR, or when choosing turbo/typegen commands.
npx skillsauth add firtoz/cf-multiworker-starter-kit multiworker-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
New fork? README Quick start — bun run quickstart, bun run setup:account (optional shared Cloudflare keys), onboard:staging, onboard:prod. Env files: cf-workers-env-local/SKILL.md.
Build, typecheck, lint, and typegen from the workspace root so Turbo can order work across packages.
bun run build
bun run typecheck
bun run lint
bun run typegen
Avoid cd apps/web && … for those unless you are debugging a single package in isolation; the root scripts define the monorepo graph. More: turborepo/SKILL.md.
Generated files are output, not source of truth. Do not manually author React Router +types, Drizzle SQL migrations, Drizzle meta/*.json snapshots, Drizzle driver-specific migration wrappers, lockfiles, or .alchemy/ state. Change the source file and run the generator/package manager instead.
For Drizzle:
packages/db/src/schema.ts for root D1, or durable-objects/<name>/src/schema.ts for package-local Durable Object SQLite.drizzle.config.ts uses the right driver (driver: "d1-http" for D1, driver: "durable-sqlite" for DO SQLite).bun run db:generate for root D1, or the DO package's db:generate script when present.During feature work, test after each meaningful sub-step with the narrowest useful check. Commit after a stable verified checkpoint, then push/update the PR. Avoid waiting until the end to discover typegen, typecheck, lint, or migration drift.
Whenever you change routes, apps/web/app/routes.ts, any alchemy.run.ts, or env / bindings:
bun run typegen (Turbo typegen: React Router .react-router/types on web, stubs elsewhere)bun run typecheckbun run lint before calling the task done (Biome may rewrite; re-run as needed)Whenever you change Drizzle schema:
bun run db:generatedb:generate if presentbun run typecheck and bun run lint from the repo rootBindings and alchemy.run.ts are the same source of truth across stages; React Router typegen does not read staged dotfiles. After route or binding changes, bun run typegen then bun run typecheck from the repo root is enough. If you edited apps/web/types/env.d.ts manually, or Alchemy resource types look wrong, confirm alchemy.run.ts and re-run bun run typecheck.
For UI, routes, canvas/pointer, or realtime changes, typecheck + lint are necessary but not sufficient.
fetch to an action does not prove the UI works.dev-server.mdc: do not start bun run dev unless the user asked or you need it to verify.
Cloudflare Env: Comes from each package’s env.d.ts and the worker resource exported from that package’s alchemy.run.ts (web uses WebBindingResources + Bindings.Runtime<… & { ASSETS }>; see apps/web/types/env.d.ts, apps/web/alchemy.run.ts).
Install from the repo root; scope adds to a single app when needed.
bun add <package>@latest --filter apps/web
# or
bun add <package>@latest --cwd apps/web
For versions shared across workspaces, package.json → workspaces.catalog is the single source of truth (alchemy, drizzle-orm, TypeScript-related pins, @cloudflare/workers-types, etc.). Add with the catalog protocol, e.g. bun add hono@catalog --cwd durable-objects/ping-do or bun add -d drizzle-orm@catalog --cwd packages/db.
^task in a package’s turbo.json runs task in workspace dependencies; prefer that over listing every other-pkg#… by hand.inputs — only that package’s files; use ^ + workspace deps to invalidate, not other packages’ source trees. turborepo/SKILL.md.dev, dev:preflight, deploy:*, destroy:*, and clean are cache: false (deploy:* so Alchemy always runs; see turborepo/SKILL.md). Use turbo run <task> --force to bypass cache for other tasks when needed.The root D1 schema source of truth is packages/db/src/schema.ts. Do not add runtime CREATE TABLE fallbacks in app loaders/actions to compensate for missed migrations.
After D1 schema changes:
bun run db:generate.bun run dev or the right stage deploy, e.g. bun run deploy:prod / deploy:staging (preview: deploy:preview with STAGE=pr-<n> from CI).packages/db/alchemy.run.ts (D1Database.migrationsDir → packages/db/drizzle). The web app binds mainDb from @internal/db/alchemy.There is no separate D1 migrate command: Alchemy applies the generated migrations when the database app runs during bun run dev or bun run deploy:*.
If local dev still reports no such table:
bun run db:generate produced or preserved the expected migration.packages/db/alchemy.run.ts still points D1Database.migrationsDir at packages/db/drizzle.bun run dev..alchemy/ app state before restarting. Do not commit .alchemy/.alchemy.run.ts and, when consumed elsewhere, exports via "./alchemy" (see Alchemy Turborepo, type-safe bindings).bun run dev and stage-specific deploy:* / destroy:* call Turbo; package scripts use alchemy dev|deploy|destroy --app <package-id>../alchemy; consumers use providerWorker.bindings.YourResource in their alchemy.run.ts for cross-script DOs. Details: cf-web-alchemy-bindings/SKILL.md, cf-durable-object-package/SKILL.md.bunx turbo gen durable-object (or copy durable-objects/ping-do/)../alchemy; wire apps/web/alchemy.run.ts and root dev / destroy:* (see multiworker-gotchas #15, cf-worker-rpc-turbo/SKILL.md).bun run dev, exercise bindings, confirm existing DOs still work.Real keys: .env.local (dev), .env.staging (staging + PR preview deploys), .env.production (prod). .env.example is documentation only. Never commit secrets. Full checklist: cf-workers-env-local/SKILL.md, Alchemy Secret.
Access in app code: import { env } from "cloudflare:workers" only.
Full deploy graph — bun run deploy:prod / deploy:staging / deploy:preview run the whole Turbo deploy:* graph (D1 + workers + web), not web-only. packages/state-hub goes first because every deploy package lists it as a devDependency, so dependsOn ^deploy:* runs the shared Alchemy state hub before siblings.
How stage env is wired — Each alchemy.run.ts reads process.env.STAGE (deployment-stage.ts). Package deploy / destroy / dev scripts call the alchemy-cli bin (--stage local|staging|prod|preview), which sets STAGE, loads the matching repo-root dotfile (+ machine account.env for Cloudflare / state token), and runs alchemy with --app from package.json → alchemy.app (see ALCHEMY_APP_IDS in worker-peer-scripts.ts). In CI, GitHub Environment secrets/vars are available to Alchemy only when the workflow passes them through an env: block (for example ${{ vars.MY_VAR }} / ${{ secrets.MY_SECRET }}), and root Turbo must allow them through turbo.json globalEnv (or task/package env config); github:sync:* does not auto-inject them into every job.
Adding a CI-used env var — Declare it in the package sidecar env.requirements.ts for setup/sync, bind or read it in the package alchemy.run.ts, document it in .env.example, then add it to root turbo.json globalEnv and the deploy workflow env: blocks that need it (main-push.yml, prod-deploy.yml, pr-deploy.yml; also preview destroy when alchemy.run.ts requires it at module scope). Run bun run typegen, typecheck, and lint.
Must-match password — requireAlchemyPassword(app) needs ALCHEMY_PASSWORD; the example chatroom path needs CHATROOM_INTERNAL_SECRET. ALCHEMY_PASSWORD must be the same for every alchemy deploy on that stage (local dotfiles and GitHub secrets).
Must-match state token (defaults to one Cloudflare account) — ALCHEMY_STATE_TOKEN is one literal bearer secret for alchemy-state-service (Alchemy: same for all deployments on the account). Align every place that hits that Worker: repo .env.staging / .env.production, all relevant GitHub Environment secrets for deploy/teardown/preview when they use secrets.ALCHEMY_STATE_TOKEN, laptop deploy sessions, and every other codebase deploying to the same CLOUDFLARE_ACCOUNT_ID with the default state store (not a fresh token “for prod”). See cf-workers-env-local §3.
github:sync:* (trusted machine only) — bun run github:sync:staging / github:sync:prod invoke alchemy-cli --stage staging|prod --app admin --entry stacks/admin.ts deploy (stacks/admin.ts) with GITHUB_SYNC_* from dotenv-cli, pushing GitHub secrets (Alchemy password, chatroom secret, CLOUDFLARE_API_TOKEN) and variables (CLOUDFLARE_ACCOUNT_ID, DEPLOY_ENABLED=true when omitted in the dotfile). Defaults to gh auth token / gh repo view — do not run this from routine CI.
Repo policy (not dotenv) — Merge toggles, rulesets (main: PR-only for writers by default with Repository admin bypass; allowRepositoryAdminBypassOnMain / requirePullRequestBeforeMerge in config/github.policy.ts), and Environment deployment rules live under github.sync.* and github.environments.*. Staging sync can apply REST + rulesets; see README GitHub admin sync reference, github-repository-settings-sync.ts, github-repo-rulesets-sync.ts.
github:env:* — Updates only GitHub RepositoryEnvironment deployment protection from the policy file (github-repository-environment-from-env.ts); alchemy-cli + GITHUB_SYNC_* from dotenv-cli supplies local process env when you run bun github:env:staging / github:env:prod.
Interactive setup — bun run quickstart (local) and bun run onboard:staging / onboard:prod (CI bootstrap — README Quick start). bun run github:setup prints an Actions overview. bun run setup / setup:local opens the variable browser; bun run setup -- --yes (or bun packages/scripts/src/setup-env.ts --yes) is for automation and only auto-fills regeneratable keys.
Further reading — Alchemy encryption password, GitHubSecret, Getting started — CLOUDFLARE_API_TOKEN.
Local dev — bun run dev runs a filtered turbo run dev (web + @internal/db + worker apps), each via alchemy-cli --stage local dev (Alchemy monorepo).
/, /visitors, /ping-do, /chat once (SSR, D1, cross-worker bindings, chat WebSockets).webUrl for 5173 but the port is dead after a crash, delete the .alchemy/pids/ pid file for the web package and ensure its .alchemy/logs/ log file exists (empty is fine) before restart—Alchemy’s log follower expects it.alchemy.run.ts, env.d.ts, or env → bun run typegen (and prod pair if needed).bun run lint passes.bun run typecheck passes.git add / git commit / git push before you finish — do not leave changes only in the working tree. 00-cloud-agent-mandatory.mdc. IDE / local chat agents usually do not commit: git-workflow.mdc.scripts#dev:preflight runs Alchemy state password checks (Turbo dev dependency).development
Fork and template gotchas (env import, routes, typegen, forms, D1, Turbo, HMR, new DO packages). Use when working on apps/web or durable-objects, or when behavior diverges from this stack’s conventions.
testing
Turborepo task configuration patterns for monorepo management. Use when configuring turbo.json tasks, setting up task dependencies, managing cache inputs/outputs, or working with cross-package dependencies in the monorepo.
development
React Router v7 routing patterns and environment variable configuration. Use whenever you touch React Router–related code (routes, links, params, loaders, actions, route config, or env in route context).
development
React patterns for callbacks, event handlers, and module-level constants. Use when writing React components, implementing event handlers, or defining constants.