Skip to content

feat(redis-worker,webapp): mollifier buffer extensions + snapshot type#3752

Open
d-cs wants to merge 9 commits into
mainfrom
mollifier-phase-3-buffer
Open

feat(redis-worker,webapp): mollifier buffer extensions + snapshot type#3752
d-cs wants to merge 9 commits into
mainfrom
mollifier-phase-3-buffer

Conversation

@d-cs
Copy link
Copy Markdown
Collaborator

@d-cs d-cs commented May 26, 2026

Summary

Buffer-side data layer used by the rest of the mollifier phase-3 stack.

  • buffer.ts gains entry inspection (getEntry), idempotency lookup (lookupIdempotency), in-place snapshot mutation (mutateSnapshot), and dwell tracking. All atomic via Lua.
  • mollifierSnapshot.server.ts: shared MollifierSnapshot type plus (de)serialise helpers.
  • Drops the entry-TTL config and its env var. The drainer is the recovery mechanism; an entry that survives the drainer should surface as a stale-sweep alert, not silently TTL away.

Adds methods to the buffer interface; nothing consumes them yet. Subsequent PRs in the stack wire trigger-time mollify, read-fallback, and mutation paths against this surface.

Test plan

  • `pnpm run typecheck --filter webapp` passes
  • `pnpm run test --filter @trigger.dev/redis-worker packages/redis-worker/src/mollifier/buffer.test.ts` passes

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

🦋 Changeset detected

Latest commit: e81ab0e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 32 packages
Name Type
@trigger.dev/redis-worker Minor
@internal/run-engine Patch
@internal/schedule-engine Patch
@trigger.dev/build Minor
@trigger.dev/core Minor
@trigger.dev/plugins Minor
@trigger.dev/python Minor
@trigger.dev/react-hooks Minor
@trigger.dev/rsc Minor
@trigger.dev/schema-to-json Minor
@trigger.dev/sdk Minor
@trigger.dev/database Minor
@trigger.dev/otlp-importer Minor
@trigger.dev/rbac Minor
trigger.dev Minor
references-ai-chat Patch
d3-chat Patch
references-d3-openai-agents Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/redis Patch
@internal/replication Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
@internal/sdk-compat-tests Patch
references-telemetry Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

This PR reworks MollifierBuffer to use per-environment ZSET queueing scored by createdAtMicros, adds Redis-backed idempotency lookup and a claim lifecycle, changes accept to return typed AcceptResult variants and support idempotency/task identifiers, introduces mutateSnapshot and casSetMetadata (with Lua atomic implementations), changes ack to mark entries materialised with a 30s grace TTL (removing accept-time TTL), extends the buffer schema, adds listing helpers and MollifierSnapshot serialization, updates @internal/redis typings and exports, and expands tests to cover ordering, idempotency, mutation/CAS atomicity, and claim safety.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description lacks the required checklist items and testing/changelog sections specified in the template. Add the complete checklist with checkboxes for contributing guide, PR title convention, and code testing. Include detailed 'Testing' and 'Changelog' sections as required by the template.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main changes: mollifier buffer extensions and a new snapshot type in redis-worker and webapp packages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mollifier-phase-3-buffer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread packages/redis-worker/src/mollifier/buffer.ts
Comment thread packages/redis-worker/src/mollifier/buffer.ts
@d-cs d-cs self-assigned this May 26, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/redis-worker/src/mollifier/buffer.ts (1)

291-304: 💤 Low value

Consider parallel fetching for consistency with listForEnvWithWatermark.

This method fetches entries sequentially with await inside a loop, while listForEnvWithWatermark uses Promise.all for parallel fetches. For small maxCount values this is fine, but parallel fetching would be more consistent and faster for larger page sizes.

♻️ Optional: parallel fetch
 async listEntriesForEnv(envId: string, maxCount: number): Promise<BufferEntry[]> {
   if (maxCount <= 0) return [];
   const runIds = await this.redis.zrevrange(
     `mollifier:queue:${envId}`,
     0,
     maxCount - 1,
   );
-  const entries: BufferEntry[] = [];
-  for (const runId of runIds) {
-    const entry = await this.getEntry(runId);
-    if (entry) entries.push(entry);
-  }
-  return entries;
+  const fetched = await Promise.all(runIds.map((runId) => this.getEntry(runId)));
+  return fetched.filter((entry): entry is BufferEntry => entry !== null);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/redis-worker/src/mollifier/buffer.ts` around lines 291 - 304, The
loop in listEntriesForEnv performs sequential awaits for getEntry causing slower
fetches for larger maxCount; change it to fetch entries in parallel similar to
listForEnvWithWatermark by mapping runIds to promises (e.g., runIds.map(id =>
this.getEntry(id))) and awaiting Promise.all, then filter out null/undefined
results before returning the BufferEntry[] so behavior and performance match
listForEnvWithWatermark.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/redis-worker/src/mollifier/buffer.ts`:
- Around line 291-304: The loop in listEntriesForEnv performs sequential awaits
for getEntry causing slower fetches for larger maxCount; change it to fetch
entries in parallel similar to listForEnvWithWatermark by mapping runIds to
promises (e.g., runIds.map(id => this.getEntry(id))) and awaiting Promise.all,
then filter out null/undefined results before returning the BufferEntry[] so
behavior and performance match listForEnvWithWatermark.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f5fa1ec2-cb73-4a04-9d09-65c1a8b8137c

📥 Commits

Reviewing files that changed from the base of the PR and between 37eeaa3 and adbb9ea.

📒 Files selected for processing (8)
  • .changeset/mollifier-buffer-extensions.md
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/mollifier/mollifierBuffer.server.ts
  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/schemas.ts
💤 Files with no reviewable changes (2)
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/mollifier/mollifierBuffer.server.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (19)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use zod for validation in packages/core and apps/webapp

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

**/*.{ts,tsx,js,jsx}: Prefer static imports over dynamic imports. Only use dynamic import() when circular dependencies cannot be resolved otherwise, code splitting is needed for performance, or the module must be loaded conditionally at runtime.
Import from @trigger.dev/core using subpaths only - never import from the root.
When writing Trigger.dev tasks, always import from @trigger.dev/sdk. Never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Add agentcrumbs markers (// @Crumbs or `#region `@crumbs) as you write code, not just when debugging. They stay on the branch throughout development and are stripped by agentcrumbs strip before merge.

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: Access environment variables through the env export of env.server.ts instead of directly accessing process.env
Use subpath exports from @trigger.dev/core package instead of importing from the root @trigger.dev/core path

Use named constants for sentinel/placeholder values (e.g. const UNSET_VALUE = '__unset__') instead of raw string literals scattered across comparisons

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
apps/webapp/**/*.server.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/**/*.server.ts: Never use request.signal for detecting client disconnects. Use getRequestAbortSignal() from app/services/httpAsyncStorage.server.ts instead, which is wired directly to Express res.on('close') and fires reliably
Access environment variables via env export from app/env.server.ts. Never use process.env directly
Always use findFirst instead of findUnique in Prisma queries. findUnique has an implicit DataLoader that batches concurrent calls and has active bugs even in Prisma 6.x (uppercase UUIDs returning null, composite key SQL correctness issues, 5-10x worse performance). findFirst is never batched and avoids this entire class of issues

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Code formatting must be enforced using Prettier before committing

Files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
packages/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying any public package (packages/* or integrations/*), add a changeset using pnpm run changeset:add. Default to patch for bug fixes and minor changes.

Files:

  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Unit tests should use vitest framework
Tests should avoid mocks or stubs and use helpers from @internal/testcontainers when Redis or Postgres are needed

**/*.test.{ts,tsx,js,jsx}: Never mock anything in tests - use testcontainers instead.
Test files should be placed next to source files (e.g., MyService.ts -> MyService.test.ts).

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
🧠 Learnings (10)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
  • packages/redis-worker/src/mollifier/schemas.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-03-29T19:16:28.864Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3291
File: apps/webapp/app/v3/featureFlags.ts:53-65
Timestamp: 2026-03-29T19:16:28.864Z
Learning: When reviewing TypeScript code that uses Zod v3, treat `z.coerce.*()` schemas as their direct Zod type (e.g., `z.coerce.boolean()` returns a `ZodBoolean` with `_def.typeName === "ZodBoolean"`) rather than a `ZodEffects`. Only `.preprocess()`, `.refine()`/`.superRefine()`, and `.transform()` are expected to wrap schemas in `ZodEffects`. Therefore, in reviewers’ logic like `getFlagControlType`, do not flag/unblock failures that require unwrapping `ZodEffects` when the input schema is a `z.coerce.*` schema.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
📚 Learning: 2026-05-05T09:38:02.512Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3523
File: apps/webapp/app/routes/api.v3.batches.ts:178-181
Timestamp: 2026-05-05T09:38:02.512Z
Learning: When reviewing code that catches `ServiceValidationError` in `*.server.ts` files, do not blindly forward `error.status` to HTTP responses, because SVEs may be thrown with non-default statuses (e.g., 400/500) and forwarding them can cause client-visible behavioral regressions (e.g., surfacing 500s to clients). Prefer a safe default response status of `error.status ?? 422`, but only after confirming via the reachable call graph that the caught `ServiceValidationError` instances are expected to carry those non-default statuses; otherwise, normalize to `422` to avoid unexpected client-visible 5xx behavior.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
📚 Learning: 2026-05-12T21:04:05.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3542
File: apps/webapp/app/components/sessions/v1/SessionStatus.tsx:1-3
Timestamp: 2026-05-12T21:04:05.815Z
Learning: In this Remix + TypeScript codebase, do not flag a server/client boundary violation when a file imports only types from a module matching `*.server`.

Specifically, it’s safe to import types using `import type { Foo } from "*.server"` or `import { type Foo } from "*.server"` because TypeScript erases type-only imports at compile time and they emit no JavaScript, so they won’t cross the Remix server/client bundle boundary.

Only raise the boundary concern for value imports (e.g., `import { Foo }` without `type`, or `import Foo`), since those produce JavaScript output.

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
📚 Learning: 2026-05-14T08:21:07.614Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3614
File: apps/webapp/app/v3/mollifier/mollifierGate.server.ts:48-52
Timestamp: 2026-05-14T08:21:07.614Z
Learning: When using Trigger.dev v3 feature flags in the webapp, prefer the existing per-org gating mechanism supported by `flag()` via the `overrides` argument. Pass `Organization.featureFlags` (from `environment.organization.featureFlags`) as the `overrides` value; overrides must take precedence over the global `featureFlag` row. Do not require schema changes or add an `orgId` field to `FlagsOptions` for per-org gating—use the overrides pattern consistently (e.g., in gate flows like `resolveOrgFlag` and any server code that threads `environment.organization.featureFlags` into the gate call).

Applied to files:

  • apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In this repo’s trigger.dev codebase, the “never mock — use testcontainers” guideline should only be applied to integration tests that talk to real external services (e.g., Redis, Postgres, S2). For unit tests that validate in-memory logic (e.g., deduplication/cache behavior in StandardRealtimeStreamsManager and similar module-boundary call counting), it is allowed to use Vitest mocks like `vi.fn()` and to stub/mock `ApiClient` objects to count calls or simulate in-process collaborators. Do not flag `vi.fn()`-based mocks as policy violations in these unit-test scenarios; reserve the rule for true external-service integration tests.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
🔇 Additional comments (22)
.changeset/mollifier-buffer-extensions.md (1)

1-7: LGTM!

apps/webapp/app/v3/mollifier/mollifierSnapshot.server.ts (1)

1-16: LGTM!

packages/redis-worker/src/mollifier/index.ts (1)

1-24: LGTM!

packages/redis-worker/src/mollifier/buffer.test.ts (1)

23-2030: LGTM!

packages/redis-worker/src/mollifier/schemas.ts (1)

30-33: LGTM!

Also applies to: 51-68

packages/redis-worker/src/mollifier/buffer.ts (17)

16-67: LGTM!


93-154: LGTM!


215-283: LGTM!


306-357: LGTM!


359-414: LGTM!


416-444: LGTM!


446-456: LGTM!


510-578: LGTM!


580-614: LGTM!


616-674: LGTM!


676-717: LGTM!


719-738: LGTM!


740-776: LGTM!


778-835: LGTM!


837-861: LGTM!


863-892: LGTM!


919-993: LGTM!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/redis-worker/src/mollifier/buffer.ts (1)

480-486: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

resetIdempotency() leaves the authoritative claim key behind.

claimIdempotency() now serializes the hot path on mollifier:claim:*, but this reset path only clears mollifier:idempotency:* and the buffered payload. After a manual reset, the same key can still come back as { kind: "resolved", runId } until the claim TTL expires, so the reset does not actually reopen the idempotency key. Please clear the claim namespace in the same reset operation, or explicitly handle the pending/resolved-claim semantics here.

Also applies to: 825-859

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/redis-worker/src/mollifier/buffer.ts` around lines 480 - 486,
resetIdempotency currently only clears the idempotency and buffered payload
keys, leaving the authoritative claim key (mollifier:claim:*) in place so a
manual reset can be immediately re-covered by a pending/resolved claim; update
resetIdempotency to also remove the corresponding claim key
(mollifier:claim:<lookupKey>) in the same Redis operation (or invoke the
existing redis-level claim reset helper if present) so the idempotency entry is
fully reopened, and apply the same fix to the other reset code path referenced
around the 825-859 block to ensure claim and idempotency namespaces are cleared
together.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/redis-worker/src/mollifier/buffer.ts`:
- Around line 480-486: resetIdempotency currently only clears the idempotency
and buffered payload keys, leaving the authoritative claim key
(mollifier:claim:*) in place so a manual reset can be immediately re-covered by
a pending/resolved claim; update resetIdempotency to also remove the
corresponding claim key (mollifier:claim:<lookupKey>) in the same Redis
operation (or invoke the existing redis-level claim reset helper if present) so
the idempotency entry is fully reopened, and apply the same fix to the other
reset code path referenced around the 825-859 block to ensure claim and
idempotency namespaces are cleared together.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 77fb6ff3-ca3e-446e-b493-88e071291045

📥 Commits

Reviewing files that changed from the base of the PR and between 8165846 and 02cfe1a.

📒 Files selected for processing (3)
  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

**/*.{ts,tsx,js,jsx}: Prefer static imports over dynamic imports. Only use dynamic import() when circular dependencies cannot be resolved otherwise, code splitting is needed for performance, or the module must be loaded conditionally at runtime.
Import from @trigger.dev/core using subpaths only - never import from the root.
When writing Trigger.dev tasks, always import from @trigger.dev/sdk. Never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Add agentcrumbs markers (// @Crumbs or `#region `@crumbs) as you write code, not just when debugging. They stay on the branch throughout development and are stripped by agentcrumbs strip before merge.

Files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Code formatting must be enforced using Prettier before committing

Files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
packages/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying any public package (packages/* or integrations/*), add a changeset using pnpm run changeset:add. Default to patch for bug fixes and minor changes.

Files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Unit tests should use vitest framework
Tests should avoid mocks or stubs and use helpers from @internal/testcontainers when Redis or Postgres are needed

**/*.test.{ts,tsx,js,jsx}: Never mock anything in tests - use testcontainers instead.
Test files should be placed next to source files (e.g., MyService.ts -> MyService.test.ts).

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
🧠 Learnings (6)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.

Applied to files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.

Applied to files:

  • packages/redis-worker/src/mollifier/index.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In this repo’s trigger.dev codebase, the “never mock — use testcontainers” guideline should only be applied to integration tests that talk to real external services (e.g., Redis, Postgres, S2). For unit tests that validate in-memory logic (e.g., deduplication/cache behavior in StandardRealtimeStreamsManager and similar module-boundary call counting), it is allowed to use Vitest mocks like `vi.fn()` and to stub/mock `ApiClient` objects to count calls or simulate in-process collaborators. Do not flag `vi.fn()`-based mocks as policy violations in these unit-test scenarios; reserve the rule for true external-service integration tests.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
🔇 Additional comments (3)
packages/redis-worker/src/mollifier/index.ts (1)

1-10: LGTM!

packages/redis-worker/src/mollifier/buffer.ts (1)

46-61: LGTM!

Also applies to: 76-77, 145-149, 379-446, 455-466, 762-823, 1056-1071

packages/redis-worker/src/mollifier/buffer.test.ts (1)

5-5: LGTM!

Also applies to: 1113-1117, 1248-1252, 1294-1298, 1342-1346, 2048-2321

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 6 additional findings.

Open in Devin Review

@d-cs d-cs force-pushed the mollifier-phase-3-buffer branch 2 times, most recently from 9298706 to f4c5b21 Compare May 27, 2026 12:21
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/redis-worker/src/mollifier/buffer.ts (2)

396-403: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use compare-and-delete for stale lookup cleanup.

lookupIdempotency() does GETgetEntry() → unconditional DEL. If two callers self-heal the same stale binding concurrently, one can clear the stale key, a fresh accept can rebind it to a new runId, and the other caller's late DEL removes that fresh binding. That reopens duplicate buffered accepts for the same idempotency key. Clear the lookup only if it still points at the stale runId you observed, ideally via a small Lua compare-and-delete helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/redis-worker/src/mollifier/buffer.ts` around lines 396 - 403, The
lookupIdempotency flow currently does GET -> getEntry() -> unconditional DEL
which can delete a newly-bound runId; change it to perform a conditional
compare-and-delete so you only remove the key if it still equals the stale runId
you observed: in lookupIdempotency (and using idempotencyLookupKeyFor and the
runId from this.redis.get) call a small Redis EVAL/Script or use a Redis client
compare-and-del helper that executes "if redis.call('GET', key) == runId then
return redis.call('DEL', key) end" so the stale key is removed only when the
stored value matches the stale runId returned by getEntry failure.

513-525: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the lookup target before returning duplicate_idempotency.

This branch treats any existing lookup binding as authoritative, but the rest of the file already acknowledges that queued entry hashes can disappear independently of auxiliary keys. If idempotencyLookupKey survives after its entry hash is evicted or manually cleaned up, accept() will return a bogus existingRunId and block the new run indefinitely. Self-heal here before returning the duplicate: if mollifier:entries:<existingRunId> is gone, delete/rebind the lookup and continue accepting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/redis-worker/src/mollifier/buffer.ts` around lines 513 - 525, The
idempotency lookup branch in accept() currently returns any existingRunId from
redis without verifying the associated entry exists, which can block new runs if
the entry was evicted; change the logic so that after reading existing =
redis.call('GET', idempotencyLookupKey) you validate whether the backing entry
key (e.g. 'mollifier:entries:' .. existing) still exists via
redis.call('EXISTS') or GET, and if that entry is missing then remove the stale
lookup (redis.call('DEL', idempotencyLookupKey)) and continue with acceptance
(i.e. do not return existing), otherwise return the existingRunId as today;
reference idempotencyLookupKey, existingRunId, runId and the
'mollifier:entries:<existingRunId>' entry key when implementing this check.
🧹 Nitpick comments (1)
packages/redis-worker/src/mollifier/buffer.test.ts (1)

563-570: ⚡ Quick win

Rename this case to describe retry priority instead of FIFO.

Popping the requeued item ahead of b and c is the intended behavior here, but it is not FIFO relative to the remaining queue. Renaming the test/comment will make the contract clearer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/redis-worker/src/mollifier/buffer.test.ts` around lines 563 - 570,
Rename the test case string and update its comment to reflect that requeued
entries get retry priority rather than preserving FIFO; replace the current
description "requeued entry pops next (RPUSH to the RPOP/tail end), preserving
FIFO" and the following comment that asserts LIST FIFO semantics with wording
that explains "requeued items are prioritized to pop next ahead of other queued
items" (mentioning LPUSH, RPOP, RPUSH and the drainer's maxAttempts behavior) so
the test name and inline comment accurately state retry priority semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/redis-worker/src/mollifier/buffer.ts`:
- Around line 396-403: The lookupIdempotency flow currently does GET ->
getEntry() -> unconditional DEL which can delete a newly-bound runId; change it
to perform a conditional compare-and-delete so you only remove the key if it
still equals the stale runId you observed: in lookupIdempotency (and using
idempotencyLookupKeyFor and the runId from this.redis.get) call a small Redis
EVAL/Script or use a Redis client compare-and-del helper that executes "if
redis.call('GET', key) == runId then return redis.call('DEL', key) end" so the
stale key is removed only when the stored value matches the stale runId returned
by getEntry failure.
- Around line 513-525: The idempotency lookup branch in accept() currently
returns any existingRunId from redis without verifying the associated entry
exists, which can block new runs if the entry was evicted; change the logic so
that after reading existing = redis.call('GET', idempotencyLookupKey) you
validate whether the backing entry key (e.g. 'mollifier:entries:' .. existing)
still exists via redis.call('EXISTS') or GET, and if that entry is missing then
remove the stale lookup (redis.call('DEL', idempotencyLookupKey)) and continue
with acceptance (i.e. do not return existing), otherwise return the
existingRunId as today; reference idempotencyLookupKey, existingRunId, runId and
the 'mollifier:entries:<existingRunId>' entry key when implementing this check.

---

Nitpick comments:
In `@packages/redis-worker/src/mollifier/buffer.test.ts`:
- Around line 563-570: Rename the test case string and update its comment to
reflect that requeued entries get retry priority rather than preserving FIFO;
replace the current description "requeued entry pops next (RPUSH to the
RPOP/tail end), preserving FIFO" and the following comment that asserts LIST
FIFO semantics with wording that explains "requeued items are prioritized to pop
next ahead of other queued items" (mentioning LPUSH, RPOP, RPUSH and the
drainer's maxAttempts behavior) so the test name and inline comment accurately
state retry priority semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 56fb6b1c-caec-4800-ad1d-898795d437f0

📥 Commits

Reviewing files that changed from the base of the PR and between e85e771 and 905becf.

📒 Files selected for processing (4)
  • .changeset/mollifier-buffer-extensions.md
  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
  • packages/redis-worker/src/mollifier/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/redis-worker/src/mollifier/index.ts
  • .changeset/mollifier-buffer-extensions.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: packages / 🧪 Unit Tests: Packages (1, 1)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

**/*.{ts,tsx,js,jsx}: Prefer static imports over dynamic imports. Only use dynamic import() when circular dependencies cannot be resolved otherwise, code splitting is needed for performance, or the module must be loaded conditionally at runtime.
Import from @trigger.dev/core using subpaths only - never import from the root.
When writing Trigger.dev tasks, always import from @trigger.dev/sdk. Never use @trigger.dev/sdk/v3 or deprecated client.defineJob.
Add agentcrumbs markers (// @Crumbs or `#region `@crumbs) as you write code, not just when debugging. They stay on the branch throughout development and are stripped by agentcrumbs strip before merge.

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)

**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (AGENTS.md)

Code formatting must be enforced using Prettier before committing

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptive describe and it blocks
Unit tests should use vitest framework
Tests should avoid mocks or stubs and use helpers from @internal/testcontainers when Redis or Postgres are needed

**/*.test.{ts,tsx,js,jsx}: Never mock anything in tests - use testcontainers instead.
Test files should be placed next to source files (e.g., MyService.ts -> MyService.test.ts).

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
packages/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying any public package (packages/* or integrations/*), add a changeset using pnpm run changeset:add. Default to patch for bug fixes and minor changes.

Files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
🧠 Learnings (6)
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
  • packages/redis-worker/src/mollifier/buffer.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In this repo’s trigger.dev codebase, the “never mock — use testcontainers” guideline should only be applied to integration tests that talk to real external services (e.g., Redis, Postgres, S2). For unit tests that validate in-memory logic (e.g., deduplication/cache behavior in StandardRealtimeStreamsManager and similar module-boundary call counting), it is allowed to use Vitest mocks like `vi.fn()` and to stub/mock `ApiClient` objects to count calls or simulate in-process collaborators. Do not flag `vi.fn()`-based mocks as policy violations in these unit-test scenarios; reserve the rule for true external-service integration tests.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.

Applied to files:

  • packages/redis-worker/src/mollifier/buffer.test.ts
🔇 Additional comments (1)
packages/redis-worker/src/mollifier/buffer.ts (1)

76-79: LGTM!

Also applies to: 232-252, 414-419, 544-548, 578-583, 618-650, 713-719, 860-865, 886-889, 1007-1011

d-cs added a commit that referenced this pull request May 27, 2026
…tedAtMicros

Address AI review findings on PR #3752:

- accept(): if an idempotency lookup survives its entry hash being evicted
  (maxmemory), the lookup is stale — rebind to the new run instead of
  returning a dead existingRunId that blocks the key forever. Mirrors the
  self-heal lookupIdempotency already does. (CodeRabbit)
- lookupIdempotency(): clear a stale lookup with a compare-and-delete
  (delMollifierKeyIfEquals Lua) so a concurrent accept that rebinds the
  key between our GET and DEL isn't clobbered. (CodeRabbit)
- schemas: default createdAtMicros to "0" so an entry written before the
  field existed (or surviving across the deploy that added it) still
  parses on pop instead of being silently dropped. (Devin)
- rename the requeue-ordering test to "retry priority" — RPUSH-to-tail
  pops the requeued entry ahead of newer items; that's deliberate retry
  priority, not FIFO relative to the rest of the queue. (CodeRabbit nit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-cs
Copy link
Copy Markdown
Collaborator Author

d-cs commented May 27, 2026

Addressed the CodeRabbit findings in decea6bec:

Outside-diff (latest review):

  • lookupIdempotency unconditional DEL → compare-and-delete. The stale-lookup self-heal now clears the key only if it still holds the runId we observed, via a small delMollifierKeyIfEquals Lua. A concurrent accept that rebinds the key between our GET and DEL can no longer be clobbered.
  • accept returned a stale existingRunId without checking the entry exists. acceptMollifierEntry now EXISTS-checks mollifier:entries:<existing> before honouring the binding; if the entry was evicted but the lookup survived, it rebinds to the new run instead of blocking the key indefinitely. Mirrors the lookupIdempotency self-heal. Covered by two new tests (rebind-on-eviction, and dedup still wins when the entry is live).
  • Requeue test naming nit. Renamed to "requeued entry gets retry priority …"; the comment now states RPUSH-to-tail gives retry priority (pops ahead of newer items), explicitly not FIFO relative to the rest of the queue.

Earlier reviews:

  • resetIdempotency left the claim key behind — already fixed earlier in this PR: resetMollifierIdempotency now DELs the mollifier:claim:* slot alongside the lookup (test: "resetIdempotency also clears the pre-gate claim slot").
  • listEntriesForEnv parallel-fetch nit — moot: listForEnvWithWatermark (the method it was asked to be consistent with) was removed, since the buffer no longer renders a paginated list view. listEntriesForEnv is now only used by the stale sweep with a bounded maxCount, so sequential fetch is fine.

The two Devin threads (createdAtMicros default, entryTtlSeconds) are resolved inline.

d-cs and others added 7 commits May 27, 2026 16:06
Adds the buffer-side data layer used by phase-3 work:
- buffer.ts gains entry inspection (getEntry), idempotency lookup
  (lookupIdempotency), in-place snapshot mutation (mutateSnapshot),
  and dwell tracking — all atomic via Lua.
- snapshot.server.ts: shared MollifierSnapshot type + (de)serialise.
- Drops the entry-TTL config — the drainer is the recovery mechanism.

Adds methods to the buffer interface; nothing consumes them yet.
Subsequent PRs in the stack wire trigger-time mollify, read-fallback,
and mutation paths against this surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the buffer extensions in this PR:
- ack() keeps the entry alive with a grace TTL as a read-fallback
  safety net. Test asserts the entry persists with materialised=true.
- fail() deletes the entry once the drainer-handler has written the
  canonical SYSTEM_FAILURE PG row. Tests assert the entry is null and
  use runOnce()'s `failed` counter as the surviving signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m ownership token

Addresses code-review feedback on the buffer's idempotency keying:

- Encode `envId` / `taskIdentifier` / `idempotencyKey` with base64url
  before concatenation so customer-supplied segments containing `:`
  cannot alias each other onto the same Redis key. Exports
  `idempotencyLookupKeyFor` so tests assert against the same encoding
  the buffer writes.
- Replace the shared `"pending"` claim marker with a caller-supplied
  ownership token (`"pending:<token>"`). `publishClaim` and
  `releaseClaim` become compare-and-set / compare-and-delete via Lua,
  so a late release from a previous claimant whose TTL expired cannot
  erase a new owner's claim.

New buffer tests cover the alias-collision case, the
encoded-key-shape contract, and the token-ownership safety properties
(stale release is a no-op, wrong-token publish is a no-op, fresh
claim survives the post-TTL-expiry stale-release race).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-worker

Drop the @trigger.dev/core entry and notice-field claim (no core change
in this PR) and trim drainer-replay wording that belongs to a later PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t gaps

Close review findings on the mollifier buffer extensions:

- mutateSnapshot(set_metadata) now bumps metadataVersion so a concurrent
  casSetMetadata conflicts instead of clobbering under a stale version
- resetIdempotency clears the pre-gate claim slot, not just the lookup,
  so a resolved/pending claim can't keep deduping past reset
- claimMollifierIdempotency guards a falsy GET before string.sub
- re-export AcceptResult (accept's public return type) and
  makeIdempotencyClaimKey from the package index
- correct stale "TTL expired" comments (no accept-time TTL anymore) and
  the mutateSnapshot doc (FAILED returns not_found, not busy)

Tests: listForEnvWithWatermark coverage (page-1, page-N, tied-score
continuation, orphan skip, empty/guard), fail clears idempotency lookup,
casSetMetadata busy on materialised entry, set_metadata/CAS version
interleave, resetIdempotency clears claim slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mark listing

The watermark listing (listForEnvWithWatermark) had zero consumers across
the whole stack — the dashboard renders buffered runs per-run via getEntry,
never a paginated queue list. With it gone, nothing left needs a sorted
set: FIFO drain is just insertion order, the stale sweep enumerates
without caring about order, and delay is materialised into PG at drain
time (never a queue sort key).

Revert the per-env queue to a Redis LIST: LPUSH on accept, RPOP on pop
(FIFO), RPUSH on requeue (transiently-failed entry pops next), LLEN for
the empty check, LRANGE for the stale-sweep enumeration. O(1) instead of
O(log N), simpler Lua. createdAtMicros stays a hash field for dwell
metrics; it is no longer a sort key.

Removes listForEnvWithWatermark + its tests; rewrites the ZSET-storage
tests as LIST-storage tests; updates orphan/requeue tests for RPOP/RPUSH.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tedAtMicros

Address AI review findings on PR #3752:

- accept(): if an idempotency lookup survives its entry hash being evicted
  (maxmemory), the lookup is stale — rebind to the new run instead of
  returning a dead existingRunId that blocks the key forever. Mirrors the
  self-heal lookupIdempotency already does. (CodeRabbit)
- lookupIdempotency(): clear a stale lookup with a compare-and-delete
  (delMollifierKeyIfEquals Lua) so a concurrent accept that rebinds the
  key between our GET and DEL isn't clobbered. (CodeRabbit)
- schemas: default createdAtMicros to "0" so an entry written before the
  field existed (or surviving across the deploy that added it) still
  parses on pop instead of being silently dropped. (Devin)
- rename the requeue-ordering test to "retry priority" — RPUSH-to-tail
  pops the requeued entry ahead of newer items; that's deliberate retry
  priority, not FIFO relative to the rest of the queue. (CodeRabbit nit)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@d-cs d-cs force-pushed the mollifier-phase-3-buffer branch from decea6b to 8bfa741 Compare May 27, 2026 15:07
@d-cs d-cs marked this pull request as ready for review May 27, 2026 15:10
d-cs and others added 2 commits May 27, 2026 16:29
…ient

The buffer client's ioredis retryStrategy used a fixed Math.min(times*50, 1000) schedule with no jitter, so a fleet of webapp instances reconnecting after the same Redis blip retried in lockstep and stampeded Redis on recovery (thundering herd) — the same lockstep-load pattern the mutate-fallback wait loop was changed to avoid.

Extract mollifierReconnectDelayMs(times, random) and apply equal jitter: a uniform pick in [base/2, base]. Bounded by the original 1s cap, so it is never slower than before, just decorrelated. Pure unit tests pin the band, the cap, and that draws spread.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove plan-tracking shorthand (Phase N, Q#, B6a) from mollifier comments and reword to plain English. Comment/test-only; no behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant