staging worktree for staging QA, then push production from the clean main worktree after the matching web deployment is live:
convex:push:all in a worktree workflow. Never pass .env.local to a production Convex deploy; use the dedicated production script instead.
Development backend
For ordinary local web work against the development Convex backend, run Next.js without deploying Convex from a feature worktree:npm run dev:with-convex pushes development Convex before starting Next.js. Use it only from the dedicated staging worktree, where the current revision is the shared staging release candidate.
Production backend
Use the production script only from the clean canonicalmain worktree after the matching main web deployment is live:
Import boundary
Convex code may import browser-safe shared modules fromsrc/shared. Convex must not import src/server, because server-only modules can break Convex bundling.
Browser auth for reactive queries
Convex queries and mutations cannot callfetch(). WorkOS / Better Auth JWKS verification therefore cannot run inside watchRoomMessages or other browser useQuery handlers.
Browser→Convex subscriptions must use short-lived HS256 tokens:
- Next BFF
GET /api/auth/convex-tokenmints a token withINTERNAL_API_SECRET(mintBrowserConvexAccessToken). ConvexAuthProviderpasses that token into Convex client queries.- Convex
requireAccessTokenverifies HS256 withcrypto.subtleonly (verifyBrowserConvexAccessToken).
INTERNAL_API_SECRET must match between the Next app env and the Convex deployment. Do not send the raw WorkOS access token into reactive queries.
Collaboration realtime is native Convex when appDataCapabilities.provider === 'convex':
watchRoomMessagesowns room transcript updates.watchConversationListVersionprovides a metadata-free invalidation signal for the sidebar.watchNotificationsis the single app-level notification subscription.
/api/v1/conversations/events long-polls alongside those subscriptions. That endpoint is a Postgres-only fallback; polling it against a Convex repository multiplies one browser connection into repeated Convex queries and authorization calls.
Bounded maintenance and list queries
Maintenance jobs and user-facing lists must advance through indexed, bounded pages.runEmptyConversationCleanup persists its pagination cursor in maintenanceCursors, and files/files:listPage is the cursor-based file listing source. Keep legacy list functions bounded during web/Convex deployment overlap; never restore a growing-table .collect() for compatibility.
Chat-delta retirement migration
RemovingconversationMessageDeltas requires a two-revision Convex rollout.
From the dedicated staging worktree, deploy the phase-5 migration revision and
run internal.chat.conversations.collapseLegacyConversationPersistence once.
Its scheduled batches fold every remaining delta into its assistant message and
mark generating messages without an active AgentRun as interrupted. Confirm the
scheduled chain has finished before deploying the following phase-5 cleanup
revision, which removes the table, queries, and crons. Never skip directly to
the cleanup revision on a deployment that may still contain delta rows.
Postgres migration 0060_retire_conversation_message_deltas performs the same
collapse and table removal transactionally. Migration 0061_agent_run_metrics
adds the runner-observation document used for Chat-versus-Work measurement.
Schema 61 retains minimum compatible runtime 60 because its metrics column is
nullable; runtimes older than 60 still query the removed delta table. Take a
database backup and treat migration 0060 as an explicit rollback boundary.