CLAUDE.md
apps/webapp/CLAUDE.mdCLAUDE.md
Quality
88/100
Scores the file, not the repository.Length
1,119 words
19 headings · 3 code blocksRepository
16k
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.1# Webapp23Remix 2.17.4 app serving as the main API, dashboard, and orchestration engine. Uses an Express server (`server.ts`).45## Verifying Changes67**Never run `pnpm run build --filter webapp` to verify changes.** Building proves almost nothing about correctness. The webapp is an app, not a public package — use typecheck from the repo root:89```bash10pnpm run typecheck --filter webapp # ~1-2 minutes11```1213Only run typecheck after major changes (new files, significant refactors, schema changes). For small edits, trust the types and let CI catch issues.1415Note: Public packages (`packages/*`) use `build` instead. See the root CLAUDE.md for details.1617## Testing Dashboard Changes with Chrome DevTools MCP1819Use the `chrome-devtools` MCP server to visually verify local dashboard changes. The webapp must be running (`pnpm run dev --filter webapp` from repo root).2021### Login2223```241. mcp__chrome-devtools__new_page(url: "http://localhost:3030")25 → Redirects to /login262. mcp__chrome-devtools__click the "Continue with Email" link273. mcp__chrome-devtools__fill the email field with "local@trigger.dev"284. mcp__chrome-devtools__click "Send a magic link"29 → Auto-logs in and redirects to the dashboard (no email verification needed locally)30```3132### Navigating and Verifying3334- **take_snapshot**: Get an a11y tree of the page (text content, element UIDs for interaction). Prefer this over screenshots for understanding page structure.35- **take_screenshot**: Capture what the page looks like visually. Use to verify styling, layout, and visual changes.36- **navigate_page**: Go to specific URLs, e.g. `http://localhost:3030/orgs/references-bc08/projects/hello-world-SiWs/env/dev/runs`37- **click / fill**: Interact with elements using UIDs from `take_snapshot`.38- **evaluate_script**: Run JS in the browser console for debugging.39- **list_console_messages**: Check for console errors after navigating.4041### Tips4243- Snapshots can be very large on complex pages (200K+ chars). Use `take_screenshot` first to orient, then `take_snapshot` only when you need element UIDs to interact.44- The local seeded user email is `local@trigger.dev`.45- Dashboard URL pattern: `http://localhost:3030/orgs/{orgSlug}/projects/{projectSlug}/env/{envSlug}/{section}`4647## Key File Locations4849- **Trigger API**: `app/routes/api.v1.tasks.$taskId.trigger.ts`50- **Batch trigger**: `app/routes/api.v1.tasks.batch.ts`51- **OTEL endpoints**: `app/routes/otel.v1.logs.ts`, `app/routes/otel.v1.traces.ts`52- **Prisma setup**: `app/db.server.ts`53- **Run engine config**: `app/v3/runEngine.server.ts`54- **Services**: `app/v3/services/**/*.server.ts`55- **Presenters**: `app/v3/presenters/**/*.server.ts`5657## Route Convention5859Routes use Remix flat-file convention with dot-separated segments:60`api.v1.tasks.$taskId.trigger.ts` -> `/api/v1/tasks/:taskId/trigger`6162## Abort Signals6364**Never use `request.signal`** for detecting client disconnects. It is broken due to a Node.js bug ([nodejs/node#55428](https://github.com/nodejs/node/issues/55428)) where the AbortSignal chain is severed when Remix internally clones the Request object. Instead, use `getRequestAbortSignal()` from `app/services/httpAsyncStorage.server.ts`, which is wired directly to Express `res.on("close")` and fires reliably.6566```typescript67import { getRequestAbortSignal } from "~/services/httpAsyncStorage.server";6869// In route handlers, SSE streams, or any server-side code:70const signal = getRequestAbortSignal();71```7273## Environment Variables7475Access via `env` export from `app/env.server.ts`. **Never use `process.env` directly.**7677For testable code, **never import env.server.ts** in test files. Pass configuration as options instead:78- `realtime/nativeRealtimeClient.server.ts` (testable service, takes config as constructor arg)79- `realtime/nativeRealtimeClientInstance.server.ts` (creates singleton with env config)8081## Run Engine 2.08283The webapp integrates `@internal/run-engine` via `app/v3/runEngine.server.ts`. This is the singleton engine instance. Services in `app/v3/services/` call engine methods for all run lifecycle operations (triggering, completing, cancelling, etc.).8485The `engineVersion.server.ts` file determines V1 vs V2 for a given environment. New code should always target V2.8687## Background Workers8889Background job workers use `@trigger.dev/redis-worker`:90- `app/v3/commonWorker.server.ts`91- `app/v3/alertsWorker.server.ts`92- `app/v3/batchTriggerWorker.server.ts`9394## Real-time9596- Socket.io: `app/v3/handleSocketIo.server.ts`, `app/v3/handleWebsockets.server.ts`97- Electric SQL: Powers real-time data sync for the dashboard9899## v3 (engine V1) removed100101v3 (engine V1: MarQS + Graphile worker) is end-of-life and its execution code is gone. The `app/v3/` directory name is historical; everything under it now serves V2. There is no V1 execution path: a `RunEngineVersion` `V1` branch (e.g. in `triggerTask.server.ts`, `cancelTaskRun.server.ts`) only rejects/finalizes gracefully so v3 clients get a clean 4xx, never a 5xx. Do not reintroduce V1. See `.claude/rules/legacy-v3-code.md` for the deprecation boundary.102103## Performance: Trigger Hot Path104105The `triggerTask.server.ts` service is the **highest-throughput code path** in the system. Every API trigger call goes through it. Keep it fast:106107- **Do NOT add database queries** to `triggerTask.server.ts` or `batchTriggerV3.server.ts`. Task defaults (TTL, etc.) are resolved via `backgroundWorkerTask.findFirst()` in the queue concern (`queues.server.ts`) - one query per request, in mutually exclusive branches depending on locked/non-locked path. Piggyback on the existing query instead of adding new ones.108- **Two-stage resolution pattern**: Task metadata is resolved in two stages by design:109 1. **Trigger time** (`triggerTask.server.ts`): Only TTL is resolved from task defaults. Everything else uses whatever the caller provides.110 2. **Dequeue time** (`dequeueSystem.ts`): Full `BackgroundWorkerTask` is loaded and retry config, machine config, maxDuration, etc. are resolved against task defaults.111- If you need to add a new task-level default, **add it to the existing `select` clause** in the `backgroundWorkerTask.findFirst()` query — do NOT add a second query. If the default doesn't need to be known at trigger time, resolve it at dequeue time instead.112- Batch triggers (`batchTriggerV3.server.ts`) follow the same pattern — keep batch paths equally fast.113114## Prisma Query Patterns115116- **Always use `findFirst` instead of `findUnique`.** Prisma's `findUnique` has an implicit DataLoader that batches concurrent calls into a single `IN` query. This batching cannot be disabled and has active bugs even in Prisma 6.x: uppercase UUIDs returning null (#25484, confirmed 6.4.1), composite key SQL correctness issues (#22202), and 5-10x worse performance than manual DataLoader (#6573, open since 2021). `findFirst` is never batched and avoids this entire class of issues.117118## Transactions119120- **Always use the `$transaction` helper from `~/db.server`, never `prisma.$transaction` (or `$replica.$transaction`) directly.** The helper wraps the raw call with tracing (an OTEL span + an `isolation_level` attribute) and boundary logging for infrastructure errors (e.g. `PrismaClientInitializationError`) that the raw client swallows. Signature: `$transaction(prisma, name?, async (tx) => { ... }, options?)`.121- Pass the isolation level via options as a string: `{ isolationLevel: "Serializable" }`. Reach for `Serializable` when a read-then-write must be atomic against concurrent transactions (e.g. a count-then-delete invariant); the loser of a race fails and can retry, which is the right trade for rare, correctness-critical paths.122- The helper returns `R | undefined` — guard the result (`if (!result) throw ...`) when callers need a definite value.123124## PAT-authenticated API routes125126- **A PAT route must resolve its target org/project scoped to the caller's membership** (`members: { some: { userId } }`, or a helper like `findProjectByRef` / `resolveOrganizationForApiUser`). A PAT is user-scoped and can name any org/project by id/slug, and the OSS RBAC fallback ability is permissive — so `ability.can(...)` alone does NOT reject a non-member on self-hosted. The RBAC `authorization` gate enforces the *role*; the membership-scoped query is the *tenant* floor. Skipping it opens cross-org access on OSS.127128## React Patterns129130- Only use `useCallback`/`useMemo` for context provider values, expensive derived data that is a dependency elsewhere, or stable refs required by a dependency array. Don't wrap ordinary event handlers or trivial computations.131- Use named constants for sentinel/placeholder values (e.g. `const UNSET_VALUE = "__unset__"`) instead of raw string literals scattered across comparisons.132
Also in triggerdotdev/trigger.dev
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| triggerdotdev/trigger.devpackages/cli-v3/CLAUDE.md · 16k | CLAUDE.md | deploymentdo-notdocs | 51/100 | 3 days ago | |
| triggerdotdev/trigger.dev.cursor/rules/executing-commands.mdc · 16k | Cursor rules | test | 49/100 | 3 days ago | |
| triggerdotdev/trigger.dev.cursor/rules/otel-metrics.mdc · 16k | Cursor rules | styledo-not | 61/100 | 3 days ago | |
| triggerdotdev/trigger.dev.cursor/rules/webapp.mdc · 16k | Cursor rules | setuptestsecurity | 55/100 | 3 days ago | |
| triggerdotdev/trigger.dev.cursor/rules/writing-tasks.mdc · 16k | Cursor rules | setupbuildarchtypes+5 | 64/100 | 3 days ago | |
| triggerdotdev/trigger.dev.github/copilot-instructions.md · 16k | Copilot instructions | teststyletypes | 32/100 | 3 days ago | |
| triggerdotdev/trigger.devAGENTS.md · 16k | AGENTS.md | setupbuildtestlint-format+8 | 88/100 | 3 days ago | |
| triggerdotdev/trigger.devapps/supervisor/CLAUDE.md · 16k | CLAUDE.md | no sections | 25/100 | 3 days ago | |
| triggerdotdev/trigger.devinternal-packages/clickhouse/CLAUDE.md · 16k | CLAUDE.md | styletypesdo-not | 61/100 | 3 days ago | |
| triggerdotdev/trigger.devinternal-packages/database/CLAUDE.md · 16k | CLAUDE.md | typesdatabasedo-not | 65/100 | 3 days ago | |
| triggerdotdev/trigger.devinternal-packages/run-engine/CLAUDE.md · 16k | CLAUDE.md | buildteststyle | 70/100 | 3 days ago | |
| triggerdotdev/trigger.devpackages/core/CLAUDE.md · 16k | CLAUDE.md | no sections | 31/100 | 3 days ago | |
| triggerdotdev/trigger.devpackages/redis-worker/CLAUDE.md · 16k | CLAUDE.md | test | 29/100 | 3 days ago | |
| triggerdotdev/trigger.devpackages/trigger-sdk/CLAUDE.md · 16k | CLAUDE.md | do-not | 54/100 | 3 days ago |
Diff against packages/cli-v3/CLAUDE.md Diff against .cursor/rules/executing-commands.mdc Diff against .cursor/rules/otel-metrics.mdc Diff against .cursor/rules/webapp.mdc Diff against .cursor/rules/writing-tasks.mdc Diff against .github/copilot-instructions.md Diff against AGENTS.md Diff against apps/supervisor/CLAUDE.md Diff against internal-packages/clickhouse/CLAUDE.md Diff against internal-packages/database/CLAUDE.md Diff against internal-packages/run-engine/CLAUDE.md Diff against packages/core/CLAUDE.md Diff against packages/redis-worker/CLAUDE.md Diff against packages/trigger-sdk/CLAUDE.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| Adit-Jain-srm/NightmareNetCLAUDE.md · 45 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 3 days ago | |
| nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.4k | CLAUDE.md | setupbuildstylearch+2 | 100/100 | 3 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 3 days ago | |
| microsoft/playwrightCLAUDE.md · 94k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| filamentphp/filamentCLAUDE.md · 32k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| bagisto/bagistoCLAUDE.md · 28k | CLAUDE.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today | |
| lollipopkit/flutter_server_boxCLAUDE.md · 8.3k | CLAUDE.md | buildteststylearch+2 | 98/100 | 3 days ago |
