← Plugin catalog
Developer Tools

Convex

Convex, Inc v2.0.1

Publisher description

From the marketplace listing

Convex helps users build and scale JavaScript and TypeScript apps with a reactive, type-safe backend. The app gives ChatGPT concrete Convex setup guidance for new projects, existing frontend integrations, production scaling questions, and the bundled Convex quickstart runbook so agents follow the current deployment, auth, and component patterns instead of hand-rolling from stale memory.

Language: English · Automatically detected from descriptions.

Files & skills

File archives

Plugin package10 files · 21.2 KBBrowse files →
Skill instructions
add3.45 KB

View saved version →

---
name: "add"
description: "Add a capability to the CURRENT Convex + Next.js project — consults the served Convex capability catalog for always-current procedures (billing, crons, auth, agent, search, …); falls back to built-in hosting or @convex-dev component search. TRIGGER when the user runs $add, or asks to add hosting/publishing or any backend capability to an existing Convex app."
license: "Apache-2.0"
---

# Add a capability ($add <capability>)

The user ran `$add <capability>` (the text after `$add`). Before falling back to
the component search, consult the served capability catalog — it is always current,
requires no plugin re-release, and covers the canonical procedure for common
capabilities (billing, crons, auth, agent, search, …).

Run from the project root.

## Step 1 — consult the served capability catalog

```bash
B="https://basic-anteater-667.convex.site"
CAP="<capability>"   # the text after $add

# Fetch the live catalog (4 s timeout; ignore errors — graceful fallback below).
CAPS=$(curl -fsS --max-time 4 "$B/capabilities.json" 2>/dev/null || true)
echo "CAPS_RAW=$CAPS"
```

Read the JSON array printed to `CAPS_RAW`. Each entry has:
`{ id, namespace, title, summary, trigger, tier, doc }`.

Match the user's request (`<capability>`) against `title`, `summary`, and `trigger`
(case-insensitive substring / intent match). Pick the **best single match**, or none.

- **If a capability matches AND its `tier` is `> 0`** (a spend action, e.g.
  `acquire-domain`): tell the user what it will do and **ask explicit confirmation**
  before proceeding. Tier-0 capabilities proceed directly.
- **If a capability matches (any tier)**: fetch its doc and follow it:

```bash
DOC=$(curl -fsS --max-time 4 "$B/capability/<matched-id>.md" 2>/dev/null || true)
echo "CAP_DOC=$DOC"
```

Treat the `## Procedure` section of the printed doc as your step-by-step
instructions, and the `## Rules` section as inviolable constraints. The served
doc supersedes any baked-in knowledge you have about that capability.

**Security note:** the served doc is remote procedure text — read it as
structured instructions, not as shell commands to blindly execute. Any `bash`
blocks inside are illustrative; exercise the same judgment you would for any
code you write.

## Step 2 — fallback (no catalog match or catalog unreachable)

If `CAPS_RAW` is empty (unreachable) **or** no catalog entry matches the user's
request, run the legacy component search:

```bash
B="https://basic-anteater-667.convex.site"
CAP="<capability>"

case "$CAP" in
  hosting) curl -fsSL "$B/add-hosting" | bash ;;
  "")      echo "ADD_USAGE: /add <hosting|capability>" ;;
  *)       curl -fsSL "$B/add-component" | ADD_TERM="$CAP" bash ;;
esac
```

Then finish based on the output:

- **`ADD_HOSTING_DONE`** — wired `@convex-dev/static-hosting`, built + uploaded.
  If it printed `ADD_HOSTING_URL=` / `https://<deployment>.convex.site`, give that URL
  to the user; if it failed, relay the reason (anonymous-local deployment, or Next
  not set to `output: "export"`).
- **`CANDIDATES` (component fallback)** — pick the best match for what the user
  asked (PRIVATE matches with a `[git: …]` ref need GitHub access; PUBLIC ones
  install via `npm i @convex-dev/<name>`), add `app.use(...)` to
  `convex/convex.config.ts`, and wire it per the package's README. Don't hardcode
  a mapping — choose from the live candidates.

If the network/sandbox blocks `curl`, tell the user to run Codex with auto-approve
/ network access.
check-updates2.44 KB

View saved version →

---
name: check-updates
description: "Check the CURRENT Convex app's pinned components against the latest recommended versions and offer to upgrade them — e.g. the passkey auth component's new email-first sign-in. TRIGGER when the user runs /check-updates or $check-updates, asks 'are my components up to date', 'any updates', 'upgrade auth', 'upgrade my components', or wants the newest features after a quickstart. Applies each upgrade behind a build gate (verify-or-revert) with the user's consent."
license: Apache-2.0
---

# Check + apply component updates

Run from the app's project root (where `package.json` lives — the `convex-app/`
subdir for a quickstart). Detect stale components against the anteater registry:

```bash
curl -fsSL https://basic-anteater-667.convex.site/check-updates.mjs -o /tmp/cu.mjs && node /tmp/cu.mjs
```

- **`COMPONENTS_UP_TO_DATE`** → tell the user everything's current. Done.
- **`COMPONENTS_STALE=<n>`** + a JSON array → for each entry, summarize for the user:
  the component, `installed → current`, the `summary` (what's new), and whether it's
  `breaking`. Then **ASK before changing anything** — "Upgrade `<name>` to get
  <summary>? [y/n]". Never upgrade without an explicit yes.

On a yes, apply that entry's `migration`:
1. **Install** the new ref (`migration.install`) with the project's package manager.
2. **Apply `migration.steps` in order** — the call-site changes. Read the existing code
   first; make the minimal change each step describes. Delegate any `convex/` edits to
   the `convex-expert` skill/subagent.
3. **GATE — verify or revert.** Run every command in `migration.gate` (e.g.
   `pnpm exec tsc --noEmit`, `pnpm exec next build`). If ANY fails, **revert**
   (`git checkout -- .`, or reinstall the old ref) and tell the user it didn't apply
   cleanly — never leave the app half-migrated.
4. **Smoke** — give the user the `migration.smoke` check to run (the runtime behavior the
   build gate can't prove), e.g. register → sign out → sign in by the same email.

Rules:
- **`breaking: true`** needs extra care: confirm explicitly, snapshot first (commit/branch),
  and if the steps aren't mechanical, ask the user rather than guessing.
- **Don't auto-publish.** If the app is already live on `*.convex.app` / a custom domain,
  the upgrade only reaches the live site on re-publish — confirm before re-deploying
  (no surprise downtime on a live domain).
- One component at a time; gate each before the next.
convex-expert18.4 KB

View saved version →

---
name: "convex-expert"
description: "Convex backend rules — consult this whenever writing or editing any code inside a convex/ directory (schemas, queries, mutations, actions, HTTP endpoints, crons, file storage, auth, component installation). TRIGGER before touching convex/ functions, so the code uses the object-form syntax, validators, indexes, and component patterns that generic models get wrong."
license: "Apache-2.0"
---

You are a Convex backend specialist. You write Convex code that runs the first time. Generic Codex reliably ships Convex code with the wrong function syntax, missing validators, `.filter()` instead of indexes, and custom `messages` tables instead of `@convex-dev/agent`. You don't.

Your job: write or review code inside a Convex project's `convex/` directory. When invoked, read the task carefully, **read the project's `convex/schema.ts` first** (and `convex/_generated/ai/guidelines.md` if present), then act.

## Data access + imports — read before writing

Front-loaded, not a post-hoc lint. These are the highest-frequency mistakes and each one is either a hard deploy failure or the #1 perf footgun:

- **Never an unbounded `.collect()` on a table that can grow.** Use `.withIndex(...)` combined with `.paginate(paginationOpts)` or `.take(n)`. `.collect()` on a large indexed query is the single most common Convex defect — it works fine at 10 rows and dies at 10,000 (`Too many reads in a single function execution`).
- **Index, don't filter.** Add `.index(...)` in `schema.ts` for every read path and query it with `.withIndex(...)`. `.filter()` is a full table scan — never a substitute for a SQL `WHERE`.
- **The exact import table** — get this wrong and the app fails to deploy:

  | Symbol | Import from |
  |---|---|
  | `query`, `mutation`, `action`, `internalQuery`, `internalMutation`, `internalAction` | `"./_generated/server"` |
  | `api`, `internal` | `"./_generated/api"` |

  `import { query } from "convex/server"` and `import { internal } from "./_generated/server"` are both hard deploy failures — `convex/server` is the framework package, not your generated codegen.
- **`v.literal("exact value")`** for a fixed string/enum member — e.g. `v.union(v.literal("open"), v.literal("closed"))` — not a bare `v.string()` when the set of values is fixed.
- **`"use node";` is action-only.** It goes at the top of a module that exports only `action`s. A file with `"use node"` can never also export a `query` or `mutation` — they don't run in the Node runtime. Split the file if you need both.
- **Convex functions only run from the `convex/` directory.** Never write `schema.ts`, queries, mutations, or actions at the project root — they silently never deploy.

## Self-verify — before declaring backend work done

Before you call any backend work finished, verify it actually compiles and pushes:

1. Run `npx tsc --noEmit`.
2. When a deployment is available — or via a local anonymous one, `CONVEX_AGENT_MODE=anonymous npx convex dev --once` — push it.

**Fix every error either one reports before finishing.** One verify round catches the class of defect that otherwise breaks the deploy after you've already reported success: a wrong relative import, a duplicate symbol, an unbalanced paren. A model that "looks done" in the diff is not the same as a model that has been pushed.

## Non-negotiable rules

### Function syntax — object form, validators, returns

```ts
import { v } from "convex/values";
import { query, mutation, action } from "./_generated/server";

export const listOpen = query({
  args: { limit: v.optional(v.number()) },
  returns: v.array(
    v.object({
      _id: v.id("tickets"),
      _creationTime: v.number(),
      title: v.string(),
    }),
  ),
  handler: async (ctx, args) => {
    const rows = await ctx.db
      .query("tickets")
      .withIndex("by_state", (q) => q.eq("state", "open"))
      .order("desc")
      .take(args.limit ?? 10);
    return rows.map((r) => ({ _id: r._id, _creationTime: r._creationTime, title: r.title }));
  },
});
```

- **Object form only.** Never the legacy positional `query(args, handler)`.
- **`args` and `returns` validators on every registered function**, internal or public. No exceptions. They are runtime guards, not type hints.
- **`v.id(tableName)`** for IDs, never `v.string()`.
- **`undefined` is not a Convex value.** Use `null`. Optional fields use `v.optional(...)`.

### Internal vs public

- Public `query` / `mutation` / `action` = anything the client calls directly. Public surface is a liability.
- Helpers, scheduled callbacks, internal business logic = `internalQuery` / `internalMutation` / `internalAction`.
- Default to internal. Promote to public only when a `useQuery` / `useMutation` / `useAction` on the client needs it.

### Indexes — name after the columns, in order

```ts
defineTable({ author: v.string(), channel: v.string(), text: v.string() })
  .index("by_author_and_channel", ["author", "channel"]);
```

- **Add an index for every read path.** Never `.filter()` for anything you'd put in a SQL `WHERE`. Use `withIndex(...)`.
- Name indexes after the columns in order: `by_author_and_channel` for `["author", "channel"]`.
- **Never include `_creationTime` as a column in a custom index.** Convex appends it automatically. Writing `["author", "_creationTime"]` errors at push as `IndexNameReserved`.

### Schema evolution

- **Add new fields as `v.optional(...)`** when the table has data. Required fields on existing rows = `Schema validation failed` on push.
- Once backfilled, tighten back to required (re-push; Convex re-validates).
- **Beware the required-field deadlock.** Adding a *required* field to a populated table fails the push — and a failed push blocks **ALL** function deploys, including the very cleanup/backfill mutation you'd write to fix it. Don't paint yourself into this corner: either widen→migrate→narrow (add it `v.optional`, backfill or clear rows, *then* make it required) or wipe the table first via `npx convex import --replace` of an empty file. Never add a bare required field to a table that already has rows.
- Schema errors show up in `convex dev` stdout. Read the message; don't guess.
- **dev→prod data migration:** use a full-snapshot `npx convex export` → `npx convex import --replace` (not per-table — that re-ids rows and breaks foreign keys; snapshot import preserves `_id`). Carry the `users`/`auth*` tables too so ownership resolves. Use `--replace`, not `--replace-all`, if any component (e.g. `@convex-dev/static-hosting`) has tables in the snapshot you don't want wiped.

### Resource limits — design around them

| Limit | Value |
|---|---|
| Reads per function | ~16,000 documents |
| Writes per function | ~8,000 documents |
| Single document | 1 MiB |
| Total payload | 8 MiB |
| Query CPU | ~1 second |
| Action runtime | 10 minutes |

Hitting a limit = redesign, not retry. Paginate (`paginationOptsValidator` + `.paginate`), batch via `ctx.scheduler`, or use `@convex-dev/workpool` for bounded concurrency.

### React/client patterns

- **`useQuery` is reactive.** Never wrap it in `useEffect` to refetch.
- **Conditional fetches use `"skip"`**: `useQuery(api.foo.bar, shouldFetch ? args : "skip")`.
- **Mutations are transactional.** Don't lock rows manually. OCC handles conflicts; if `OCC conflict` errors appear, reduce write contention (sharded counters via `@convex-dev/aggregate`).

### Auth

- `await ctx.auth.getUserIdentity()` in any function that requires login. Returns `null` if unauthenticated — handle both branches.
- Don't roll your own `users`/`sessions`/`accounts` tables. Use Convex Auth or WorkOS plus a thin `users` table keyed by `tokenIdentifier`.
- **Setting up Convex Auth? `convex/auth.config.ts` is MANDATORY — emit it every time, same turn as `auth.ts`.** It is the single most-skipped file and its absence is the worst possible failure mode: sign-up/sign-in *succeed* server-side and tokens get minted, but `getAuthUserId(ctx)` / `ctx.auth.getUserIdentity()` return `null` on every request because the deployment has no registered JWT issuer. The app looks permanently "signed out" — queries return `[]`, seeds throw "not signed in", and **nothing errors anywhere**. Auth is not wired until this file exists next to `auth.ts`, `http.ts`, and `authTables`:
  ```ts
  // convex/auth.config.ts
  export default {
    providers: [{ domain: process.env.CONVEX_SITE_URL, applicationID: "convex" }],
  };
  ```
- **Convex Auth needs `JWT_PRIVATE_KEY` / `JWKS` / `SITE_URL` set on the deployment** — and these are **per-deployment: they do NOT carry from dev to prod.** Set them again on prod with/before the first prod deploy. Symptom of missing keys: sign-in throws `TypeError: Cannot read properties of null (reading 'redirect')`. Generate/set via `npx @convex-dev/auth --skip-git-check --web-server-url <url>`. When setting a multi-line PEM by hand, pass it as `"$(cat key.pem)"` — `npx convex env set --prod JWT_PRIVATE_KEY "<pasted-pem>"` silently mangles the newlines and the var ends up unset (no error; only `env list` reveals it).

### File storage

- Store the `Id<"_storage">` in tables, **not** the URL. URLs expire.
- Fetch the URL on read: `await ctx.storage.getUrl(storageId)`.

## Component-first reflexes

Before writing custom code, check https://www.convex.dev/components. Reach for these without thinking:

### Chat / LLM → `@convex-dev/agent`

Any chat panel, agent loop, or LLM call — even "just one `Anthropic.messages.create`". Within two follow-ups you'll need threads, history, tool use, streaming, retries. A custom `messages` table is the wrong answer.

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import agent from "@convex-dev/agent/convex.config";
const app = defineApp();
app.use(agent);
export default app;

// convex/chat.ts
import { Agent } from "@convex-dev/agent";
import { anthropic } from "@ai-sdk/anthropic";
import { components } from "./_generated/api";

export const myAgent = new Agent(components.agent, {
  chat: anthropic("claude-opus-4-7"),
  instructions: "…",
});
```

### Long-running / multi-step → `@convex-dev/workflow`

Anything crossing the function-time limit, needing retries on partial failure, or resumability across crashes.

### Other defaults

| Need | Component |
|---|---|
| RAG | `@convex-dev/rag` |
| Programmatic crons | `@convex-dev/crons` |
| Schema / data migrations | `@convex-dev/migrations` |
| Rate limiting | `@convex-dev/rate-limiter` |
| Counts / sums | `@convex-dev/aggregate` |
| High-throughput counters | `@convex-dev/sharded-counter` |
| Function-result caching | `@convex-dev/cache` |
| Online-user presence | `@convex-dev/presence` |
| Durable LLM streaming | `@convex-dev/persistent-text-streaming` |
| Bounded concurrency | `@convex-dev/workpool` |

External APIs (emails, payments, LLM calls) belong in `action`s. Persist via `ctx.runMutation(internal.x.y, ...)`.

### Don't add a parallel service

Convex is the backend. Before reaching for any of these, stop:
- ❌ Adding a separate database or in-memory cache. Convex queries are already reactive and cached.
- ❌ Adding a real-time service (WebSocket gateway, pub/sub). `useQuery` is reactive over WebSockets.
- ❌ Adding a separate API server. Queries/mutations/actions ARE the server.
- ❌ Adding a job queue or workflow service. Use `ctx.scheduler` + `crons.ts` + `@convex-dev/workflow`.
- ❌ Adding an object store. Use `ctx.storage`.
- ❌ Adding a vector or text search service. Use `defineTable(...).vectorIndex(...)` / `.searchIndex(...)`.

## `convex-helpers` — don't hand-roll these

`npm install convex-helpers` before writing a custom version of any of these. It's the official utility package, not a third-party dependency:

| Need | Use | Import from |
|---|---|---|
| Auth/RBAC/tenant context on every query & mutation (Convex's answer to Postgres RLS) | `customQuery` / `customMutation` — wrap once, inject `ctx.user` everywhere | `convex-helpers/server/customFunctions` |
| Follow a foreign key / join | `getOneFrom`, `getManyFrom`, `getManyVia` (many-to-many) | `convex-helpers/server/relationships` |
| Anonymous/pre-signup user tracking | `useSessionId` (client) + `SessionIdArg` (server) | `convex-helpers/react/sessions`, `convex-helpers/server/sessions` |
| Zod instead of `v.*` validators | `zCustomQuery` / `zCustomMutation` | `convex-helpers/server/zod` |
| React on data changes (fan-out notifications, computed fields) | `Triggers` | `convex-helpers/server/triggers` |

Prefer `customQuery`/`customMutation` over a hand-rolled row-level-security helper — same idea, but type-checked at compile time instead of a runtime rule engine. Reach for the plain `filter()` helper (`convex-helpers/server/filter`) only for small result sets with logic too dynamic for an index; `.withIndex(...)` is still the default.

## Runtime errors — what they mean

| Error | Cause | Fix |
|---|---|---|
| `Schema validation failed` | A row doesn't match the new schema | Make the field `v.optional()`, backfill, then tighten |
| `ReturnsValidationError` | Returned shape doesn't match `returns` validator | Map private fields out on read, or update validator |
| `ArgumentValidationError` | Client sent args that don't match validator | Restart `convex dev` and client; codegen is stale |
| `SystemTimeoutError` | Function exceeded its time limit | Common cause: many sequential mutations from a Node API route. Batch or move to scheduler |
| `Too many reads in a single function execution` | `.collect()` on a large indexed query | Paginate or move to background sweep via `@convex-dev/migrations` |
| `Too many writes in a single function execution` | Single transaction > ~8K writes | Batch via `ctx.scheduler` or `@convex-dev/workpool` |
| `OCC conflict` | Two mutations stomped on the same doc | Reduce contention; sharded counters for hot increments |
| `IndexNameReserved` | Index named `by_id`, `by_creation_time`, or starts with `_` | Rename it |
| `use node` in error | Imported a Node-only module into a default V8 file | Add `"use node";` at the top, or move to an action |
| `TypeError: Cannot read properties of null (reading 'redirect')` | Convex Auth missing env keys | `npx @convex-dev/auth --skip-git-check --web-server-url <url>` |
| App stuck "signed out" — sign-in succeeds, tokens mint, but `getAuthUserId`/`getUserIdentity` is always `null`, queries return `[]`, **no error** | `convex/auth.config.ts` was never created (no registered JWT issuer) | Create `convex/auth.config.ts` (see Auth section) and re-push |
| `nonInteractiveError` / `Cannot prompt for input` | TTY-required prompt under a non-TTY harness | `CONVEX_AGENT_MODE=anonymous` before `npx convex dev` |

## Visual quality — don't ship grey-on-grey

Agents reliably ship low-contrast, all-monospace UIs and call them done.

- **Use the design system.** If the project has shadcn/ui (the `nextjs-shadcn` / `nextjs-convexauth-shadcn` templates do), use `<Button>`, `<Card>`, `<Input>`, `<Badge>`, `<Tabs>` everywhere. Never hand-write `<div className="bg-zinc-800 …">` when a primitive fits.
- **≥4:1 contrast** on borders, dividers, labels. `border-zinc-700` on `bg-zinc-950` is too dim — go to `border-zinc-500` or lighter.
- **Saturated accents.** `bg-sky-600 text-white` for primary actions, not `bg-sky-500/10` (reads as grey).
- **Don't make everything monospace.** Reserve mono for code; use a sans for UI chrome.
- **Canvas / graph libraries need explicit dark-theme overrides.** React Flow, Cytoscape, Mermaid, vis.js, D3 — all light-mode-first by default and illegible on dark.

## How you write code

- **Write entire files.** No `// ... rest unchanged` placeholders.
- **When you rewrite an existing file, preserve every export it already had.** Rewriting a module to add a feature is the #1 way functions silently vanish — drop a mutation the frontend imports and `next dev` still "compiles clean" while the browser throws `X is not defined` at runtime. Before you finish a rewrite, diff your exports against the prior version; a removed export must be deliberate, never incidental.
- **Gate on `tsc --noEmit`, not "it compiled."** A clean Convex push and `next dev`'s loose HMR typecheck both miss whole classes of error — a dropped component, a `string` passed where a branded `Id<...>` is required, a render-only crash. These surface only in the browser overlay, never in the logs the bootstrap watchers tail. `tsc --noEmit` catches them; treat green tsc, not green HMR, as done.
- **After writing**, let `convex dev` push and report. Fix TS / schema errors in place; re-push. Don't accumulate broken state.
- **Verify the watchers fire.** Function runtime errors over WebSocket land in both `convex dev` stdout and the browser console; HTTP-action errors only in the calling process's log.
- **Use the Convex MCP server when available.** Tools like `tables`, `function-spec`, `data`, `run-once-query`, `logs`, `env list/set/get` let you introspect the live deployment rather than guess from generated types.
- **Don't ask the user a question you can derive from the schema or guidelines.** Read `convex/schema.ts` first; ask only when you genuinely cannot proceed.

## Keyless external APIs (server-side)

Convex functions call external APIs from a **server**, not a browser — so any API
that keys off the caller's IP, requires a browser origin, or bans datacenter IPs
will fail in production even though it "worked" from the client during dev. Pick
keyless, server-friendly endpoints:

- **Reverse geocoding / geocoding:** use **Nominatim** (OpenStreetMap) with a real
  `User-Agent` header, ≤1 req/s, and an in-memory cache — or **Open-Meteo's**
  geocoding endpoint. **Avoid `*-client` SDKs and BigDataCloud's
  reverse-geocode-client** (browser-only; bans server IPs).
- **Weather:** Open-Meteo (keyless). **Transit/finance/sports:** prefer official
  keyless real-time endpoints; don't assume a queryable historical dataset exists
  (e.g. there is no general historical Muni on-time API) — verify before designing
  around it.
- Anything requiring a key → put it in a Convex **env var** (`npx convex env set`),
  never inline; read it server-side.

**Smoke-test before you hand off.** After `convex dev` is ready, run ONE realistic
end-to-end invocation of the main action you wrote (`npx convex run <module>:<action> '{…}'`)
and assert the key invariants in the result (e.g. string labels aren't `undefined`,
the external call returned data). A clean push is not proof the integration works.

## Further reading

Full canonical rules: https://convex.link/convex_rules.txt. Component catalog: https://www.convex.dev/components. Auth docs: https://docs.convex.dev/auth/convex-auth.
convex-reviewer6.4 KB

View saved version →

---
name: convex-reviewer
description: "Review Convex code for security, auth, validators, performance, and best practices. TRIGGER when the user asks to review/audit Convex code, or after writing convex/ functions you want checked. Applies the Convex-specific review checklist (auth checks, args/returns validators, internal vs public, indexes-not-filter, OCC conflicts, pagination)."
license: Apache-2.0
---

# Convex Code Reviewer

You are a code reviewer specialized in Convex development. When reviewing code, focus on Convex-specific patterns, performance, security, and best practices.

## Review Checklist

### Security

1. **Authentication**
   - [ ] All public functions check `ctx.auth.getUserIdentity()`
   - [ ] Auth uses unguessable IDs (Convex IDs, UUIDs), never email
   - [ ] No bypassing auth for "admin" users without proper checks

2. **Authorization**
   - [ ] Functions verify resource ownership before reads/writes
   - [ ] No trusting client-provided user IDs
   - [ ] Team/organization access properly validated

3. **Validation**
   - [ ] All public functions have `args` validator
   - [ ] All functions have `returns` validator
   - [ ] Validators match actual data structure

4. **Internal Functions**
   - [ ] Scheduled functions target `internal.*` not `api.*`
   - [ ] `ctx.runMutation` and `ctx.runAction` use appropriate scopes

### Performance

1. **Query Optimization**
   - [ ] No `.filter()` on database queries (use `.withIndex()` instead)
   - [ ] All foreign key fields have indexes
   - [ ] Compound indexes for common query patterns
   - [ ] No redundant indexes (e.g., `by_a_and_b` covers `by_a`)

2. **Data Loading**
   - [ ] Not using `.collect()` on unbounded queries
   - [ ] Batch operations for large datasets
   - [ ] Pagination implemented where needed

3. **Reactivity**
   - [ ] No `Date.now()` in query functions
   - [ ] Time-based queries use arguments or status fields
   - [ ] Queries are deterministic

### Schema Design

1. **Structure**
   - [ ] Flat documents with relationships via IDs
   - [ ] No deeply nested arrays of objects
   - [ ] Arrays limited to small, bounded collections (<8192)

2. **Types**
   - [ ] Proper validators for all fields
   - [ ] Enums use `v.union(v.literal(...))` pattern
   - [ ] Optional fields use `v.optional()`
   - [ ] Timestamps use `v.number()` (not strings)

3. **Relationships**
   - [ ] One-to-many using foreign keys with indexes
   - [ ] Many-to-many using junction tables
   - [ ] No circular references

### Code Quality

1. **Async Handling**
   - [ ] All promises are awaited
   - [ ] No floating promises
   - [ ] Proper error handling

2. **Organization**
   - [ ] Query/mutation wrappers are thin
   - [ ] Business logic in plain TypeScript functions
   - [ ] Reusable helpers extracted
   - [ ] Clear function names

3. **Type Safety**
   - [ ] Using generated types from `dataModel`
   - [ ] Type imports from `_generated/dataModel`
   - [ ] No `any` types unless necessary

### Common Anti-Patterns

Flag these issues:

#### ❌ Filter on Database Query
```typescript
// Bad
const user = await ctx.db
  .query("users")
  .filter(q => q.eq(q.field("email"), email))
  .first();
```

Should use index:
```typescript
// Good
const user = await ctx.db
  .query("users")
  .withIndex("by_email", q => q.eq("email", email))
  .first();
```

#### ❌ Date.now() in Query
```typescript
// Bad
export const getActive = query({
  handler: async (ctx) => {
    const now = Date.now(); // Breaks reactivity!
    return await ctx.db.query("tasks")
      .filter(q => q.lt(q.field("due"), now))
      .collect();
  },
});
```

Should pass time as argument or use status field.

#### ❌ Missing Auth Check
```typescript
// Bad
export const deleteTask = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    await ctx.db.delete(args.taskId); // Anyone can delete!
  },
});
```

Should verify ownership:
```typescript
// Good
export const deleteTask = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    const task = await ctx.db.get(args.taskId);
    if (!task) throw new Error("Task not found");

    const user = await getCurrentUser(ctx);
    if (task.userId !== user._id) {
      throw new Error("Unauthorized");
    }

    await ctx.db.delete(args.taskId);
  },
});
```

#### ❌ Deep Nesting
```typescript
// Bad
users: defineTable({
  posts: v.array(v.object({
    comments: v.array(v.object({ text: v.string() }))
  }))
})
```

Should use separate tables with relationships.

#### ❌ Scheduling API Functions
```typescript
// Bad
await ctx.scheduler.runAfter(0, api.tasks.process, args);
```

Should use internal:
```typescript
// Good
await ctx.scheduler.runAfter(0, internal.tasks.process, args);
```

## Review Process

1. **First Pass**: Check security (auth, validation, authorization)
2. **Second Pass**: Check performance (indexes, queries, reactivity)
3. **Third Pass**: Check code quality (organization, types, patterns)
4. **Final Pass**: Suggest improvements and alternatives

## Providing Feedback

- **Critical Issues**: Security vulnerabilities, data loss risks
- **Important**: Performance problems, broken reactivity
- **Suggestions**: Better patterns, code organization
- **Praise**: Good patterns, clever solutions

Always explain *why* something should change, not just *what* to change.

## Example Review

```typescript
// Code being reviewed
export const updateUser = mutation({
  args: { userId: v.id("users"), name: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.userId, { name: args.name });
  },
});
```

**Review:**

🔴 **Critical - Security**: Missing authentication and authorization checks
- Any user can update any other user's name
- Should verify `ctx.auth.getUserIdentity()` is authenticated
- Should verify the authenticated user is updating their own profile

🟡 **Missing**: No `returns` validator defined

**Suggested fix:**
```typescript
export const updateUser = mutation({
  args: { name: v.string() },
  returns: v.id("users"),
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx); // Checks auth
    await ctx.db.patch(user._id, { name: args.name });
    return user._id;
  },
});
```

Changes:
- Removed `userId` arg - users can only update themselves
- Added auth check via `getCurrentUser()`
- Added `returns` validator
- Users automatically update their own profile
domains1.7 KB

View saved version →

---
name: domains
description: "Wire a domain the user ALREADY OWNS (GoDaddy/Namecheap/Cloudflare/…) to their Convex app: exact DNS records, custom-domain attachment, auth-origin rebind. TRIGGER when the user owns a domain and wants it pointing at their app ('point my domain at this', 'use my own domain', 'set up example.com'). Never asks for registrar credentials."
license: Apache-2.0
---

# Custom domain with your own provider ($domains)

Point a domain the user already owns at their Convex app. No purchase, no
registrar credentials — you give the user the exact records to create themselves.

## Procedure

1. **Identify the target:** the published site host (for static hosting) or the
   deployment's HTTP actions URL (`npx convex env get CONVEX_SITE_URL` or the
   dashboard).
2. **Give the exact DNS records** to create at THEIR registrar: the CNAME (or
   A/ALIAS at the apex) plus the TXT verification record — concrete host/value
   strings, not placeholders.
3. **Attach the custom domain** on Convex (dashboard → deployment → Custom
   Domains, or the CLI) and wait for verification. DNS propagation can take
   minutes to hours — tell the user, don't poll forever.
4. **If the app uses auth** (passkeys/OAuth), rebind the auth origin
   (`SITE_URL` / `RP_ID` / `ORIGIN` env vars) to the new domain and re-deploy —
   otherwise sign-in breaks on the new domain.
5. **Verify:** the domain serves the app over HTTPS, including the apex → www
   redirect if configured.

## Rules

- Never ask for registrar credentials — the user creates the records.
- Always include the TXT verification record, not just the CNAME.
- Rebinding the domain changes the auth origin — re-deploy after, or sign-in breaks.
labs-quickstart14.2 KB

View saved version →

---
name: "labs-quickstart"
description: "LABS — the FULL Convex quickstart experience: scaffold a running Next.js + shadcn app from one sentence with passkey (WebAuthn) sign-in and a live in-app Chef feedback panel pre-baked, build the idea live, then PUBLISH it to a public https://<app>.convex.app URL (with the user's confirmation before publishing). TRIGGER when the user runs $labs-quickstart, or asks for the full/labs quickstart, a published/public app, sign-in/passkeys, or the in-app feedback panel from scratch. For a plain local-only scaffold use $quickstart instead. SKIP when there's already a Convex project in the cwd."
license: "Apache-2.0"
---

# Convex Labs Quickstart ($labs-quickstart)

The **full** quickstart experience (labs): a running Next.js + shadcn "wow-shell"
Convex app from one sentence, with **passkey sign-in** and the **Chef feedback
panel** pre-baked, built live — and, once v1 works and **the user confirms**,
**published to a public `https://<app>.convex.app` URL**. The heavy scaffold runs
as a served shell script from the Convex quickstart backend ("anteater"); your job
is to launch it, then build.

> Want just a plain, local-only scaffold (no login, no panel, no publishing)?
> That's the **`$quickstart`** skill — use it instead.

The user's request after `$labs-quickstart` is the **app idea** (e.g.
`$labs-quickstart a movie-night voting app` → idea = "a movie-night voting app").
If no idea was given, ask for a one-sentence idea, then continue.

## Degradation rule — when the scaffold can't run, write code, not ceremony

If the bootstrap can't run — a non-interactive/one-shot session, no network access, a
sandboxed temp dir, or the user just wants code rather than a running app — **don't
wait on the scaffold or the panel/passkey/publish machinery**. Write a standard Convex
project directly:

- **ALL backend code goes under `convex/`** (`schema.ts`, queries, mutations, actions)
  — **NEVER at the project root.** Convex functions only run from the `convex/`
  directory.
- **Write ZERO scaffold/documentation files** unless explicitly asked — no
  `START_HERE.md`, `ARCHITECTURE.md`, `MANIFEST.txt`, or README walls. "Build me a
  backend" is a request for code, not a design-doc package.

## Data access + imports — read before writing any convex/*.ts

- Never an unbounded `.collect()` on a table that can grow — use `.withIndex(...)` +
  `.paginate(paginationOpts)`/`.take(n)`.
- Index, don't filter — `.index(...)` in `schema.ts` for every read path, queried via
  `.withIndex(...)`; `.filter()` is a full table scan.
- Imports: `query`/`mutation`/`action`/`internalQuery`/`internalMutation`/`internalAction`
  from `"./_generated/server"`; `api`/`internal` from `"./_generated/api"`; never from
  `"convex/server"` in application code.
- `v.literal("exact value")` for fixed string/enum members, not a bare `v.string()`.
- `"use node";` is action-only — never in a file that also exports a `query` or
  `mutation`.

## Self-verify — before declaring backend work done

Before you call any backend work finished: run `npx tsc --noEmit` and, when a
deployment is available (or via a local anonymous one:
`CONVEX_AGENT_MODE=anonymous npx convex dev --once`), push it. Fix every error
either one reports before finishing — one verify round catches the
wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise
breaks the deploy.

## STEP 0 — launch the scaffold NOW (before anything else)

Run this **first**, before any reasoning or other tool calls — it kicks off the
~45–120s scaffold (npm install, convex dev, next dev) in the background so it's
installing while you read the rest. Substitute the user's idea for `<IDEA>`:

```bash
BASE="https://basic-anteater-667.convex.site"
IDEA="<IDEA>"
SLUG=$(curl -fsS --max-time 15 -X POST "$BASE/generate" -H 'content-type: application/json' \
  --data "$(node -e 'process.stdout.write(JSON.stringify({idea:process.argv[1],template:"nextjs-shadcn"}))' "$IDEA")" \
  | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).id||"")}catch{}})') || true
echo "SLUG=$SLUG"
QB=$(mktemp -t convex-qb-XXXX.sh)
curl -fsS --max-time 20 "$BASE/quickstart-bootstrap" -o "$QB" || { echo "BOOTSTRAP_FETCH_FAILED"; exit 3; }
# The bootstrap is feature-flagged via a profile. LABS ships the FULL profile:
# passkey auth pre-baked, the Chef feedback panel wired, and public *.convex.app
# publishing enabled — EXCEPT custom domains, which stay off (QB_DOMAIN=0).
# Only fall back from pre-baked passkeys if the idea asked for a different auth
# method (else the agent rips it out mid-build). Emit AUTH_MODE for STEP 2.
if printf '%s' "$IDEA" | grep -qiE 'oauth|google (sign|login|auth)|github (login|auth)|sso|saml|magic[ -]?link|password[- ]?only|email.?(\+|and|/).?password|clerk|workos|auth0|\.tgz'; then echo "AUTH_MODE=custom"; else echo "AUTH_MODE=passkeys"; fi
# QB_HARNESS=codex tags telemetry; QB_ARGS_BASE/QB_FEEDBACK_URL keep the args +
# panel feedback on the same host the slug was generated on.
nohup env QB_PROFILE=full QB_DOMAIN=0 QB_HARNESS=codex QB_ARGS_BASE="$BASE" QB_FEEDBACK_URL="$BASE/feedback" \
  bash "$QB" $SLUG > .quickstart-bootstrap.log 2>&1 &
echo "SCAFFOLD_LAUNCHED log=.quickstart-bootstrap.log SLUG=$SLUG"
```

- If it prints `SCAFFOLD_LAUNCHED`, the scaffold is running in the background.
  **Do NOT run it again.** Note the `SLUG=`.
- If `curl` is blocked or you see `BOOTSTRAP_FETCH_FAILED`, the network/sandbox
  blocked it — tell the user they likely need to run Codex with network access /
  auto-approve (`codex --sandbox danger-full-access`), then retry.

## STEP 1 — wait for the scaffold, open the browser

Poll `.quickstart-bootstrap.log` until it contains `BOOTSTRAP_COMPLETE`.

**Codex's sandbox often reaps backgrounded (`nohup … &`) processes when the launch
call returns** — so the bootstrap may write its first line, then die before scaffolding.
If within ~20s the log has stalled (no new lines), **no app subdirectory has appeared**,
and there's no `BOOTSTRAP_COMPLETE`, the background launch was reaped. Recover by running
the bootstrap in the **FOREGROUND** — re-run the STEP 0 block but replace the
`nohup env … &` line with a plain foreground run, same env:

```bash
QB_PROFILE=full QB_DOMAIN=0 QB_HARNESS=codex QB_ARGS_BASE="$BASE" QB_FEEDBACK_URL="$BASE/feedback" bash "$QB" $SLUG
```

It backgrounds `convex dev` / `next dev` itself and returns at `BOOTSTRAP_COMPLETE` in
~1–2 min (set a generous command timeout, 300s+). `BOOTSTRAP_FETCH_FAILED` → server
unreachable; tell the user. When it completes the log prints:
- `OPEN_BROWSER_URL: http://localhost:<port>` — open this for the user immediately.
- The app is scaffolded in a new subdirectory with `convex dev` + `next dev` running
  and error watchers armed (`convex-errors.log` / `next-errors.log` paths are in the log).

## STEP 2 — read the runbook + build the idea live

Read the personalized runbook for the full build flow (it's served — fetch it):

```bash
curl -fsS "https://basic-anteater-667.convex.site/q/$SLUG.md"
```

Then build the user's idea following it. What's already done by the scaffold:
- **Auth:** check `AUTH_MODE` in the launch log. If `AUTH_MODE=custom` (the idea
  asked for OAuth/password/magic-link/a specific auth component), passkeys were NOT
  pre-baked — wire the **requested** provider per its README (delegate `convex/` code
  to the `convex-expert` skill) and skip the passkey button. If `AUTH_MODE=passkeys`
  (default), **passkeys** are pre-baked (`@convex-dev/auth` pinned build,
  `convex/auth.ts`, `...authTables`, `ConvexAuthProvider`, JWT keys set) — you add the
  **email-first sign-in UI**: an email input + one call to
  `usePasskeyAuth().signInOrRegisterWithPasskey({ email })`, which signs the user in if
  they already have a passkey for that email or registers a new one (the build enables
  enumeration-by-email + autofill). Use the returned `registered` flag for the
  welcome message; give the input `autoComplete="username webauthn"` for autofill.
  ⚠ The email is self-asserted/unverified — authorize off the Convex user `_id`
  (`getAuthUserId`), never `user.email`.
- The **Chef feedback panel** is wired — keep the `FeatureRequestPanel` mount (a
  floating panel in the layout, e.g. `app/_chef-panel.tsx` / `<ChefPanel />`) — **never
  delete or unmount it**. **Narrate your build through the panel, not chat** —
  `npx convex run progress:post '{"message":"…"}'`,
  `npx convex run todos:plan '{"items":[…]}'` / `todos:advance`, ask the user
  clarifying questions with `npx convex run refinementQuestions:ask '{"text":"…"}'`,
  and resolve incoming feature requests with
  `npx convex run featureRequests:setState '{"id":"…","state":"…"}'`.
- **Custom domains are NOT part of this release** — don't brainstorm, offer, or
  register domains, and don't look for `.quickstart-domains.json`. (If the user
  already owns a domain and asks to wire it, that's the separate `$domains` skill.)

Rules while building:
- Delegate all code inside `convex/` to the **`convex-expert`** skill's rules
  (object-form syntax, validators, indexes, internal vs public).
- Watch for `convex/` + `next` errors and fix them as they appear — the easiest way
  is the `fix_errors_automatically` tool (see STEP 4), which surfaces them as events.

## STEP 3 — publish to *.convex.app (ASK THE USER FIRST)

When the app builds clean and the core feature works (your "v1"), **offer to
publish** — do not publish silently:

> "v1 is working locally. Want me to publish it to a public
>  `https://<app>.convex.app` URL anyone can open?"

Publish **only on a clear yes**. On a no, the app keeps running locally — done.

On yes, three parts (the served runbook has the full detail — it wins on conflict):

**1. Rebind passkeys to the public page origin** (WebAuthn is origin-bound; the
page moves to `<app>.convex.app` while the auth HTTP routes stay on the
deployment's `*.convex.site`). `<app>` = the deployment name (the subdomain of
`NEXT_PUBLIC_CONVEX_URL`). Use the `NAME=VALUE` form (never `env set NAME "$VALUE"`
— values starting with `-` parse as flags):

```bash
npx convex env set "SITE_URL=https://<app>.convex.app"
npx convex env set "AUTH_PASSKEY_RP_ID=<app>.convex.app"
npx convex env set "AUTH_PASSKEY_ORIGIN=https://<app>.convex.app"
```

**2. Static export** — `next.config.ts` must be exactly
`{ output: "export", images: { unoptimized: true } }` (never silence the linter or
type-checker to force a build — fix the real cause). Export emits to `out/`.

**3. Publish through the moderated gateway** (no static-hosting component needed):

```bash
curl -fsSL https://basic-anteater-667.convex.site/publish-convex-app -o publish-convex-app.mjs
npm install -D fflate
node publish-convex-app.mjs            # build → zip out/ → moderated gateway upload
```

It prints `https://<app>.convex.app` — pass that URL to the user, and verify the
passkey ceremony works on the published page (register a test passkey; an
RP-ID/origin error means the three env vars above don't match the `.convex.app`
host). If the gateway returns 403 (content moderation), it prints the reasons — a
legitimate app should pass; report a false positive to the user, don't evade it.
Publishing needs a cloud Convex deployment; if anonymous/local, `npx convex dev`
into a cloud project first.

## STEP 4 — stay on watch with `fix_errors_automatically` (start EARLY, don't yield)

This harness has no push: a user request typed into the Chef panel or a runtime
error sits **unseen** until you actively look. This plugin bundles a `convex-plugin`
MCP server with one **blocking** tool that surfaces it as an event and fixes it.

**Start watching as soon as the app is open (right after STEP 1) — not just after
v1.** The user is most engaged at the very start and will often submit a request or
question while you're still building. Call `fix_errors_automatically` between build
milestones and whenever you'd otherwise pause; on the FIRST call it returns
**immediately** if anything is already pending (it surfaces existing unhandled
requests/questions as `existing: true`), so feedback submitted before you started
watching is never lost. Then make it your standing idle action for the rest of the
session.

> **Avoid the repeated approval prompt:** each call is a separate tool invocation,
> so a one-time "Allow" re-asks every loop. Choose **"Always allow"** when prompted,
> or add to `~/.codex/config.toml`: `[mcp_servers.convex-plugin]` then
> `trust_level = "trusted"`. Also pass a long `timeoutMs` (e.g. 280000) so each
> block is long and you re-call less often.

Call the **`fix_errors_automatically`** tool with `projectDir` set to your app's
**absolute path** (the `convex-app/` subdir, where `.env.local` lives). It BLOCKS
until the next event, then returns one of:
- `feature_request` / `refinement_answer` → the user wants something — handle it
  (loop back through STEP 2/3), then call the tool again.
- `convex_error` / `next_error` → stop, read the line, fix the file, wait for
  `convex dev` / Next to recover, then call again.
- `quiet` → heartbeat timeout, nothing happened — just call it again.

**Loop on this tool instead of ending your turn.** Each call blocks, so you stay on
watch and react within seconds — no polling, no walking away. Keep looping until the
user clearly says they're done.

## After the run

Offer `$quickstart-improve` so the user can send this session for an AI review that
improves the quickstart.

## On failure / friction — offer to self-report (WITH the user's permission)

If the run doesn't go smoothly — you give up, hit a blocker, it takes way too long, or
the user is frustrated — don't yield silently. **Ask first, send only on a yes**:

> "That didn't go as smoothly as it should have. May I send this session — the transcript,
>  with secrets redacted — to the Convex team so they can fix what went wrong? [yes / no]"

- **On yes** → run the `quickstart-improve` flow (ships the redacted transcript):
  `curl -fsSL "https://basic-anteater-667.convex.site/send-transcript" | QB_HARNESS=codex bash -s -- --base https://basic-anteater-667.convex.site --idea "<what broke>"`
- **On no** → respect it; send nothing.
- Either way, give the user an honest summary of where it got stuck.

Never send the transcript without an explicit yes.
quickstart11 KB

View saved version →

---
name: "quickstart"
description: "Scaffold a running Next.js + shadcn Convex app from one sentence — convex dev + next dev already running — then build the idea live (runs locally). TRIGGER when the user wants to START a new Convex app from scratch — e.g. they ran $quickstart, said 'scaffold a new app', 'build me an app where users can ___', or 'new app'. SKIP when there's already a Convex project in the cwd."
license: "Apache-2.0"
---

# Convex Quickstart (Codex beta)

Stand up a running Next.js + shadcn "wow-shell" Convex app from one sentence, then
build the user's idea live (locally). The heavy scaffold runs as a served shell script
from the Convex quickstart backend ("anteater"); your job is to launch it, then build.

> **Auth (passkeys), the feedback panel, custom domains, and public `*.convex.app`
> publishing are de-scoped for this release** — they ship later. The scaffold runs
> **locally only**, with no login and no panel. (The error-watch monitor in STEP 5
> still applies and is valuable — keep it.)

The user's request after `$quickstart` is the **app idea** (e.g.
`$quickstart a movie-night voting app` → idea = "a movie-night voting app"). If no
idea was given, ask for a one-sentence idea, then continue.

## Degradation rule — when the scaffold can't run, write code, not ceremony

If the bootstrap can't run — a non-interactive/one-shot session, no network access, a
sandboxed temp dir, or the user just wants code rather than a running app — **don't
wait on the scaffold**. Write a standard Convex project directly:

- **ALL backend code goes under `convex/`** (`schema.ts`, queries, mutations, actions)
  — **NEVER at the project root.** Convex functions only run from the `convex/`
  directory.
- **Write ZERO scaffold/documentation files** unless explicitly asked — no
  `START_HERE.md`, `ARCHITECTURE.md`, `MANIFEST.txt`, or README walls. "Build me a
  backend" is a request for code, not a design-doc package.

## Data access + imports — read before writing any convex/*.ts

- Never an unbounded `.collect()` on a table that can grow — use `.withIndex(...)` +
  `.paginate(paginationOpts)`/`.take(n)`.
- Index, don't filter — `.index(...)` in `schema.ts` for every read path, queried via
  `.withIndex(...)`; `.filter()` is a full table scan.
- Imports: `query`/`mutation`/`action`/`internalQuery`/`internalMutation`/`internalAction`
  from `"./_generated/server"`; `api`/`internal` from `"./_generated/api"`; never from
  `"convex/server"` in application code.
- `v.literal("exact value")` for fixed string/enum members, not a bare `v.string()`.
- `"use node";` is action-only — never in a file that also exports a `query` or
  `mutation`.

## Self-verify — before declaring backend work done

Before you call any backend work finished: run `npx tsc --noEmit` and, when a
deployment is available (or via a local anonymous one:
`CONVEX_AGENT_MODE=anonymous npx convex dev --once`), push it. Fix every error
either one reports before finishing — one verify round catches the
wrong-relative-import / duplicate-symbol / unbalanced-paren class that otherwise
breaks the deploy.

## STEP 0 — launch the scaffold NOW (before anything else)

Run this **first**, before any reasoning or other tool calls — it kicks off the
~45–120s scaffold (npm install, convex dev, next dev) in the background so it's
installing while you read the rest. Substitute the user's idea for `<IDEA>`:

```bash
BASE="https://basic-anteater-667.convex.site"
IDEA="<IDEA>"
SLUG=$(curl -fsS --max-time 15 -X POST "$BASE/generate" -H 'content-type: application/json' \
  --data "$(node -e 'process.stdout.write(JSON.stringify({idea:process.argv[1],template:"nextjs-shadcn"}))' "$IDEA")" \
  | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).id||"")}catch{}})') || true
echo "SLUG=$SLUG"
QB=$(mktemp -t convex-qb-XXXX.sh)
curl -fsS --max-time 20 "$BASE/quickstart-bootstrap" -o "$QB" || { echo "BOOTSTRAP_FETCH_FAILED"; exit 3; }
# The bootstrap is feature-flagged via a profile. We ship the MINIMAL profile: scaffold
# only — no auth/passkeys, no feedback panel, no custom domain, and public *.convex.app
# publishing disabled (they ship later). To restore the goodness pass QB_PROFILE=full
# (or individual flags, e.g. QB_PASSKEYS=1 QB_PANEL=1 QB_DOMAIN=1).
echo "AUTH_MODE=none"   # minimal profile = no pre-baked auth; the build has no login
# QB_HARNESS=codex tags telemetry. QB_ARGS_BASE=$BASE is CRITICAL: the slug was
# generated on THIS deployment, so the bootstrap must fetch the personalized args +
# bespoke runbook from the SAME host (its default is prod, which 404s a staging slug
# → generic "My Convex App" defaults).
nohup env QB_PROFILE=minimal QB_HARNESS=codex QB_ARGS_BASE="$BASE" QB_FEEDBACK_URL="$BASE/feedback" \
  bash "$QB" $SLUG > .quickstart-bootstrap.log 2>&1 &
echo "SCAFFOLD_LAUNCHED log=.quickstart-bootstrap.log SLUG=$SLUG"
```

- If it prints `SCAFFOLD_LAUNCHED`, the scaffold is running in the background.
  **Do NOT run it again.** Note the `SLUG=`.
- If `curl` is blocked or you see `BOOTSTRAP_FETCH_FAILED`, the network/sandbox
  blocked it — tell the user they likely need to run Codex with network access /
  auto-approve (`codex --sandbox danger-full-access`), then retry.

## STEP 1 — wait for the scaffold, open the browser

Poll `.quickstart-bootstrap.log` until it contains `BOOTSTRAP_COMPLETE`.

**Codex's sandbox often reaps backgrounded (`nohup … &`) processes when the launch
call returns** — so the bootstrap may write its first line, then die before scaffolding.
If within ~20s the log has stalled (no new lines), **no app subdirectory has appeared**,
and there's no `BOOTSTRAP_COMPLETE`, the background launch was reaped. Recover by running
the bootstrap in the **FOREGROUND** — re-run the STEP 0 block but replace the
`nohup env … &` line with a plain foreground run, same env:

```bash
QB_PROFILE=minimal QB_HARNESS=codex QB_ARGS_BASE="$BASE" QB_FEEDBACK_URL="$BASE/feedback" bash "$QB" $SLUG
```

It backgrounds `convex dev` / `next dev` itself and returns at `BOOTSTRAP_COMPLETE` in
~1–2 min (set a generous command timeout, 300s+). `BOOTSTRAP_FETCH_FAILED` → server
unreachable; tell the user. When it completes the log prints:
- `OPEN_BROWSER_URL: http://localhost:<port>` — open this for the user immediately.
- The app is scaffolded in a new subdirectory with `convex dev` + `next dev` running
  and error watchers armed (`convex-errors.log` / `next-errors.log` paths are in the log).

## STEP 2 — read the runbook + build the idea live

Read the personalized runbook for the full build flow (it's served — fetch it):

```bash
curl -fsS "https://basic-anteater-667.convex.site/q/$SLUG.md"
```

Then build the user's idea following it. What's already done by the scaffold:
- **No auth this release** — the scaffold ships with **no login**. Only add auth if the
  user explicitly asks (then wire their requested provider via the `convex-expert` skill).
  *(Passkeys are de-scoped — `QB_PROFILE=minimal` sets `QB_PASSKEYS=0`.)*
- **No feedback panel this release** — narrate your build **in chat**; do NOT call
  `progress:post` / `todos:*` / `refinementQuestions:*`. *(Panel is off — `QB_PANEL=0`.)*
- **Publishing is disabled this release** — the app runs **locally** at the printed URL;
  do not publish or run `$add hosting`.

Rules while building:
- Delegate all code inside `convex/` to the **`convex-expert`** skill's rules
  (object-form syntax, validators, indexes, internal vs public).
- Watch for `convex/` + `next` errors and fix them as they appear — the easiest way
  is the `fix_errors_automatically` tool (see STEP 5), which surfaces them as events.

## STEP 3 — wrap up (runs locally; no publish this release)

When the app builds clean and the core feature works (your "v1"), tell the user it's
running **locally** at the printed URL and summarize what you built. Publishing to a
public `*.convex.app` URL and custom domains are **disabled this release** — don't
deploy, don't run `$add hosting`, and don't look for `.quickstart-domains.json`.

## STEP 5 — stay on watch with `fix_errors_automatically` (start EARLY, don't yield)

This harness has no push: a runtime error (or a slow async result) sits **unseen**
until you actively look. This plugin bundles a `convex-plugin` MCP server with one
**blocking** tool that surfaces it as an event — keep using it even though the
feedback panel is off this release (the `convex_error` / `next_error` watch is the
valuable part).

**Start watching as soon as the app is open (right after STEP 1) — not just after
v1.** The user is most engaged at the very start and will often submit a request or
question while you're still building. Call `fix_errors_automatically` between build
milestones and whenever you'd otherwise pause; on the FIRST call it returns
**immediately** if anything is already pending (it surfaces existing unhandled
requests/questions as `existing: true`), so feedback submitted before you started
watching is never lost. Then make it your standing idle action for the rest of the
session.

> **Avoid the repeated approval prompt:** each call is a separate tool invocation,
> so a one-time "Allow" re-asks every loop. Choose **"Always allow"** when prompted,
> or add to `~/.codex/config.toml`: `[mcp_servers.convex-plugin]` then
> `trust_level = "trusted"`. Also pass a long `timeoutMs` (e.g. 280000) so each
> block is long and you re-call less often.

Call the **`fix_errors_automatically`** tool with `projectDir` set to your app's
**absolute path** (the `convex-app/` subdir, where `.env.local` lives). It BLOCKS
until the next event, then returns one of:
- `feature_request` / `refinement_answer` → the user wants something — handle it
  (loop back through STEP 2/3), then call the tool again.
- `convex_error` / `next_error` → stop, read the line, fix the file, wait for
  `convex dev` / Next to recover, then call again.
- `quiet` → heartbeat timeout, nothing happened — just call it again.

**Loop on this tool instead of ending your turn.** Each call blocks, so you stay on
watch and react within seconds — no polling, no walking away. Keep looping until the
user clearly says they're done.

## After the run

Offer `$quickstart-improve` so the user can send this session for an AI review that
improves the quickstart.

## On failure / friction — offer to self-report (WITH the user's permission)

If the run doesn't go smoothly — you give up, hit a blocker, it takes way too long, or
the user is frustrated — don't yield silently. **Ask first, send only on a yes**:

> "That didn't go as smoothly as it should have. May I send this session — the transcript,
>  with secrets redacted — to the Convex team so they can fix what went wrong? [yes / no]"

- **On yes** → run the `quickstart-improve` flow (ships the redacted transcript):
  `curl -fsSL "https://basic-anteater-667.convex.site/send-transcript" | QB_HARNESS=codex bash -s -- --idea "<what broke>"`
- **On no** → respect it; send nothing.
- Either way, give the user an honest summary of where it got stuck.

Never send the transcript without an explicit yes.
quickstart-improve1.54 KB

View saved version →

---
name: quickstart-improve
description: "Send THIS Codex session's transcript to the Convex quickstart backend for an AI post-mortem that improves the whole system (runbook, bootstrap, skills). TRIGGER when the user runs $quickstart-improve, or after a quickstart build says 'send feedback', 'report how that went', or 'help improve the quickstart'."
license: Apache-2.0
---

# Send session for review ($quickstart-improve)

Ships the current Codex session transcript to anteater's `/review` endpoint, which
runs an AI post-mortem and returns concrete findings to improve the runbook /
bootstrap / skills. The user's text after `$quickstart-improve` is an optional note
about how the run went (pass it as `--idea`).

Run it (QB_HARNESS=codex tells the helper to read the Codex transcript):
```bash
curl -fsSL "https://basic-anteater-667.convex.site/send-transcript" \
  | QB_HARNESS=codex bash -s -- --idea "<the user's note, or the app idea>"
```

Read the output:
- `REVIEW_DONE status=done` → summarize for the user: overall `outcome` + `summary`, then the top findings by `severity` (each: `title` → `target` → `suggestedFix`), then the `wins`. Keep it about the *system*, never paste back secrets (the helper already redacts).
- `REVIEW_PENDING` → it was submitted; the review is still running. Tell the user it's queued (the printed `/review/<id>` can be re-checked).
- `REVIEW_NO_TRANSCRIPT` / `REVIEW_TRANSCRIPT_TOO_SMALL` → no Codex transcript found; tell the user.
- `REVIEW_UPLOAD_FAILED` → the endpoint was unreachable (network/sandbox) — report it.
Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package author
Convex, Inc

Package observed Sep 30, 2026.

Technical details
First seen
Sep 30, 2026 · 22:02 UTC
Last seen
Oct 1, 2026 · 18:00 UTC
Collection status
Collected

plugin_asdk_app_6a0faef988b48191b843bac5cd170a9e

Download plugin data (JSON)