Skip to content

Errors (versions)#3187

Draft
matt-aitken wants to merge 20 commits intomainfrom
errors-add-version-filter
Draft

Errors (versions)#3187
matt-aitken wants to merge 20 commits intomainfrom
errors-add-version-filter

Conversation

@matt-aitken
Copy link
Member

  • Added versions filtering on the Errors list and page
  • Added errors stacked bars to the graph on the individual error page

@changeset-bot
Copy link

changeset-bot bot commented Mar 5, 2026

⚠️ No Changeset found

Latest commit: d1110c5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@matt-aitken matt-aitken marked this pull request as draft March 5, 2026 23:24
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

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
  • 🔍 Trigger review

Walkthrough

Adds end-to-end error-group versioning, filtering, and alerting support. Frontend: new LogsVersionFilter, exported VersionsDropdown, status/version filters, chart legend class prop, ConfigureErrorAlerts UI, alert configuration routes, and storybook UnorderedList. Backend: presenters accept versions/statuses, ErrorGroupState types, ErrorGroupActions, DeliverErrorGroupAlertService, ErrorAlertEvaluator, alert worker tasks, and alert-channel presenter. ClickHouse: new per-version and "since" query builders. Database: Prisma/schema and SQL migrations for ErrorGroupState and alert config. Email template and email client integration for error-group alerts.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~180 minutes

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and incomplete, missing most required sections from the template including issue reference, checklist, testing details, and screenshots. Complete the PR description by filling out all sections from the template: add issue reference, confirm checklist items, describe testing steps, add changelog details, and include screenshots where applicable.
Docstring Coverage ⚠️ Warning Docstring coverage is 17.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Errors (versions)' is vague and generic, using non-descriptive phrasing that doesn't clearly convey the main changes to someone scanning commit history. Provide a more specific title that describes the primary change, such as 'Add version filtering to errors list and stacked charts' or similar descriptive phrasing.

✏️ 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 errors-add-version-filter

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.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

Copy link
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 found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

@matt-aitken matt-aitken force-pushed the errors-add-version-filter branch from cd9b5ea to e609e8b Compare March 16, 2026 23:30
Copy link
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.

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (2)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx (1)

560-574: ⚠️ Potential issue | 🔴 Critical

Missing ERROR_GROUP case in alertTypeTitle will cause runtime error.

The ProjectAlertType enum now includes ERROR_GROUP (added in this PR's schema changes), but alertTypeTitle doesn't handle it. When an alert channel with ERROR_GROUP type is displayed in the table (line 237), this will throw an error.

🐛 Proposed fix
 export function alertTypeTitle(alertType: ProjectAlertType): string {
   switch (alertType) {
     case "TASK_RUN":
       return "Task run failure";
     case "TASK_RUN_ATTEMPT":
       return "Task attempt failure";
     case "DEPLOYMENT_FAILURE":
       return "Deployment failure";
     case "DEPLOYMENT_SUCCESS":
       return "Deployment success";
+    case "ERROR_GROUP":
+      return "Error group";
     default: {
       throw new Error(`Unknown alertType: ${alertType}`);
     }
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
around lines 560 - 574, The switch in alertTypeTitle(ProjectAlertType) is
missing a handler for the new "ERROR_GROUP" enum value and will throw for that
case; update the alertTypeTitle function to add a case for "ERROR_GROUP" (e.g.,
case "ERROR_GROUP": return "Error group";) so all ProjectAlertType variants are
handled and the default throw is avoided when ERROR_GROUP alerts are rendered
(used elsewhere where alerts are displayed).
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts (1)

64-85: ⚠️ Potential issue | 🔴 Critical

getSummary() declares ErrorGroupSummary as return type but returns an incomplete object without state.

The getSummary() method at line 249 declares a return type of Promise<ErrorGroupSummary | undefined>, where ErrorGroupSummary.state is required. However, the method returns an object (lines 276–285) that omits the state field entirely. While the call() method does assign state after retrieval (line 149), this violates the type contract of getSummary(). Either make state optional in the type definition, include state in getSummary()'s return object, or update getSummary()'s return type to reflect the incomplete object it actually returns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts` around lines 64
- 85, getSummary() claims to return ErrorGroupSummary but omits the required
state field; update getSummary() to include a state property in its returned
object (either populated from the same data source used by call() or a sensible
default with fields like status, resolvedAt, resolvedInVersion, resolvedBy,
ignoredUntil, ignoredReason, ignoredByUserId, ignoredUntilOccurrenceRate,
ignoredUntilTotalOccurrences) so the returned object matches the
ErrorGroupSummary type, and remove any subsequent reassignment in call() that
assumes state is missing.
🧹 Nitpick comments (4)
internal-packages/clickhouse/src/index.ts (1)

248-263: Add @crumbs tracing annotations for the new error-query paths.

Please add crumb comments around the newly added errors query builder exposure to align with repository tracing standards.

As per coding guidelines, **/*.{ts,tsx,js}: “Add crumbs as you write code using // @crumbs comments or // #region @crumbs blocks for agentcrumbs debug tracing”.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal-packages/clickhouse/src/index.ts` around lines 248 - 263, The new
errors getter (get errors) that exposes multiple query-builder helpers
(getGroups, getInstances, getHourlyOccurrences, affectedVersionsQueryBuilder,
listQueryBuilder, occurrencesListQueryBuilder, createOccurrencesQueryBuilder,
createOccurrencesByVersionQueryBuilder, occurrenceCountSinceQueryBuilder,
activeErrorsSinceQueryBuilder, occurrenceCountsSinceQueryBuilder) needs
repository-standard tracing crumbs; wrap the errors block with // `@crumbs`
comments (or a // `#region` `@crumbs` ... // `#endregion` `@crumbs` block) immediately
before and after the getter so agentcrumbs can trace calls to these functions,
ensuring the comments surround the get errors { ... } block and reference the
same function names for clarity.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx (1)

630-632: Consider a more reliable approach for closing the dialog after submission.

Using setTimeout(onClose, 100) is fragile—if the submission takes longer than 100ms to start, the dialog may close before the request begins. Consider using the fetcher's state to close the dialog when submission completes:

♻️ Suggested alternative
// In CustomIgnoreForm, use useEffect to close on success:
import { useEffect } from "react";

// ...

useEffect(() => {
  if (fetcher.state === "idle" && fetcher.data?.ok) {
    onClose();
  }
}, [fetcher.state, fetcher.data, onClose]);

// Remove setTimeout from onSubmit

Or simply close immediately on submit and let the optimistic UI handle it if that's the intended behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
around lines 630 - 632, The current onSubmit handler uses setTimeout(onClose,
100) which is fragile; remove the setTimeout call in the onSubmit prop of
CustomIgnoreForm and instead close the dialog when the fetcher completes by
adding a useEffect in the CustomIgnoreForm component that watches fetcher.state
and fetcher.data (e.g., useEffect(() => { if (fetcher.state === "idle" &&
fetcher.data?.ok) onClose(); }, [fetcher.state, fetcher.data, onClose]));
alternatively, if you intend optimistic UI, call onClose() immediately in
onSubmit and omit the timeout.
apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts (1)

30-46: Prefer a type alias for the payload shape.

This new model should use type instead of interface to stay aligned with the repo's TypeScript rules.

As per coding guidelines, **/*.{ts,tsx}: Use types over interfaces for TypeScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts` around
lines 30 - 46, Replace the interface declaration ErrorAlertPayload with a type
alias using the same shape (e.g., type ErrorAlertPayload = { ... }); keep the
property names and nested error object unchanged (including channelId,
projectId, classification, and the error fields like fingerprint, environmentId,
environmentName, taskIdentifier, errorType, errorMessage, sampleStackTrace,
firstSeen, lastSeen, occurrenceCount) so all references to ErrorAlertPayload and
ErrorAlertClassification continue to work; update any imports/exports if
necessary to reflect the type alias.
apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts (1)

16-26: Prefer type aliases for these internal models.

AlertableError and ResolvedEnvironment should use type aliases here to match the repo's TypeScript convention.

As per coding guidelines, **/*.{ts,tsx}: Use types over interfaces for TypeScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts` around
lines 16 - 26, Replace the two internal interfaces with type aliases to match
repo convention: convert interface AlertableError and interface
ResolvedEnvironment into type AlertableError = { ... } and type
ResolvedEnvironment = { ... }, keeping the exact same property names and types
(classification, error, environmentName) and (id, type, displayName)
respectively; update any local references if needed but do not change the shape
or visibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts`:
- Around line 130-145: getState is being called without the task identifier so
it can return the wrong ErrorGroupState when the same fingerprint exists under
multiple tasks; stop fetching summary and state in parallel and instead first
await this.getSummary(...) to read the taskIdentifier, then call
this.getState(environmentId, taskIdentifier, fingerprint) (or otherwise pass the
task identifier into getState) when you run the remaining Promise.all for
getAffectedVersions, getRunList and getState so state lookup is disambiguated by
taskIdentifier.

In `@apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts`:
- Around line 104-112: The helper `#scheduleErrorAlertEvaluation` currently
enqueues a job using the same id pattern "evaluateErrorAlerts:${projectId}"
that's used by the evaluator's delayed self-chain, causing deduplication/no-op
if a future run is already queued; change the id for this immediate kick to be
unique (for example append a timestamp or a "now" suffix like
"evaluateErrorAlerts:${projectId}:now:${Date.now()}") so alertsWorker.enqueue
always creates a distinct job when triggered immediately after channel
creation/edit; keep the job name "v3.evaluateErrorAlerts" and payload identical,
only alter the id generation logic in `#scheduleErrorAlertEvaluation`.

In `@apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts`:
- Around line 239-243: The code logs and throws the raw webhook URL
(webhookProperties.data.url) from the channel object (type
ProjectAlertChannelType), which can leak embedded credentials; update
deliverErrorGroupAlert.server.ts to sanitize/redact webhookProperties.data.url
before any logging or including it in thrown errors (e.g., replace
host/path/query credentials with "[REDACTED]" or strip auth/query params) and
ensure any thrown Error messages or processLogger entries use the redacted
value; apply the same change for the other occurrences around the webhook
handling block (also referenced at the second occurrence near lines 310-316) so
no raw webhook URL is emitted.

In `@apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts`:
- Around line 178-197: The total-occurrence unignore check uses
context.occurrencesSince (window-only) so a threshold like
ignoredUntilTotalOccurrences won’t fire across multiple evaluation windows;
change the logic to compute the cumulative occurrences since the alert was
ignored and compare that to state.ignoredUntilTotalOccurrences. Specifically,
replace the context.occurrencesSince check with a call that gets counts since
state.ignoredAt (or otherwise compute occurrences since state.ignoredAt using
getOccurrenceCountsSince/your occurrence-count helper) and use that cumulative
count in the if that references state.ignoredUntilTotalOccurrences and
state.ignoredAt.
- Around line 102-125: The loop enqueues alerts to every envChannels entry on
every evaluator run, ignoring per-channel intervals; inside the for (const
channel of envChannels) loop (before calling alertsWorker.enqueue) add a guard
that respects the channel's configured interval by comparing it to the
evaluator's current run interval (e.g., currentRunIntervalMinutes or
evaluationIntervalMinutes): only enqueue when the current run aligns with the
channel interval (for example channel.intervalMinutes ===
currentRunIntervalMinutes or channel.intervalMinutes % currentRunIntervalMinutes
=== 0), otherwise continue; update any related loops mentioned (the other
occurrences around the same pattern at the referenced ranges) to use the same
interval-check guard.
- Around line 30-35: The constructor for ErrorAlertEvaluator has its _prisma and
_replica defaults reversed causing writes (e.g., updateErrorGroupStates) to go
to the read-only client; swap the defaults so _prisma defaults to prisma and
_replica defaults to $replica. Update the constructor parameter
order/assignments for ErrorAlertEvaluator accordingly so any write operations
use the primary Prisma client (_prisma = prisma) and reads use the replica
(_replica = $replica).
- Around line 372-387: Remove the stray comma-expression "await this, ..." so
the Prisma update is actually awaited: locate the call to
this._prisma.errorGroupState.update (the block that sets status: "UNRESOLVED"
and clears ignored/resolved fields) and change the statement to await the update
promise directly (i.e., await this._prisma.errorGroupState.update(...)) so the
database write completes before continuing (e.g., before calling selfChain()).

In `@internal-packages/emails/emails/alert-error-group.tsx`:
- Line 58: Change the default export of the component function Email to a named
export (use "export function Email(...)" instead of "export default function
Email(...)") and then update the corresponding import in the emails index module
to use a named import (e.g. import { Email } from '...') or rename on import if
needed; also update any usages that relied on the default import to reference
the named export.

---

Outside diff comments:
In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts`:
- Around line 64-85: getSummary() claims to return ErrorGroupSummary but omits
the required state field; update getSummary() to include a state property in its
returned object (either populated from the same data source used by call() or a
sensible default with fields like status, resolvedAt, resolvedInVersion,
resolvedBy, ignoredUntil, ignoredReason, ignoredByUserId,
ignoredUntilOccurrenceRate, ignoredUntilTotalOccurrences) so the returned object
matches the ErrorGroupSummary type, and remove any subsequent reassignment in
call() that assumes state is missing.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx:
- Around line 560-574: The switch in alertTypeTitle(ProjectAlertType) is missing
a handler for the new "ERROR_GROUP" enum value and will throw for that case;
update the alertTypeTitle function to add a case for "ERROR_GROUP" (e.g., case
"ERROR_GROUP": return "Error group";) so all ProjectAlertType variants are
handled and the default throw is avoided when ERROR_GROUP alerts are rendered
(used elsewhere where alerts are displayed).

---

Nitpick comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx:
- Around line 630-632: The current onSubmit handler uses setTimeout(onClose,
100) which is fragile; remove the setTimeout call in the onSubmit prop of
CustomIgnoreForm and instead close the dialog when the fetcher completes by
adding a useEffect in the CustomIgnoreForm component that watches fetcher.state
and fetcher.data (e.g., useEffect(() => { if (fetcher.state === "idle" &&
fetcher.data?.ok) onClose(); }, [fetcher.state, fetcher.data, onClose]));
alternatively, if you intend optimistic UI, call onClose() immediately in
onSubmit and omit the timeout.

In `@apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts`:
- Around line 30-46: Replace the interface declaration ErrorAlertPayload with a
type alias using the same shape (e.g., type ErrorAlertPayload = { ... }); keep
the property names and nested error object unchanged (including channelId,
projectId, classification, and the error fields like fingerprint, environmentId,
environmentName, taskIdentifier, errorType, errorMessage, sampleStackTrace,
firstSeen, lastSeen, occurrenceCount) so all references to ErrorAlertPayload and
ErrorAlertClassification continue to work; update any imports/exports if
necessary to reflect the type alias.

In `@apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts`:
- Around line 16-26: Replace the two internal interfaces with type aliases to
match repo convention: convert interface AlertableError and interface
ResolvedEnvironment into type AlertableError = { ... } and type
ResolvedEnvironment = { ... }, keeping the exact same property names and types
(classification, error, environmentName) and (id, type, displayName)
respectively; update any local references if needed but do not change the shape
or visibility.

In `@internal-packages/clickhouse/src/index.ts`:
- Around line 248-263: The new errors getter (get errors) that exposes multiple
query-builder helpers (getGroups, getInstances, getHourlyOccurrences,
affectedVersionsQueryBuilder, listQueryBuilder, occurrencesListQueryBuilder,
createOccurrencesQueryBuilder, createOccurrencesByVersionQueryBuilder,
occurrenceCountSinceQueryBuilder, activeErrorsSinceQueryBuilder,
occurrenceCountsSinceQueryBuilder) needs repository-standard tracing crumbs;
wrap the errors block with // `@crumbs` comments (or a // `#region` `@crumbs` ... //
`#endregion` `@crumbs` block) immediately before and after the getter so agentcrumbs
can trace calls to these functions, ensuring the comments surround the get
errors { ... } block and reference the same function names for clarity.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e7e10a3c-d2f2-429c-85bb-c4d210930768

📥 Commits

Reviewing files that changed from the base of the PR and between cd9b5ea and e609e8b.

📒 Files selected for processing (21)
  • apps/webapp/app/components/logs/LogsVersionFilter.tsx
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • apps/webapp/app/components/runs/v3/RunFilters.tsx
  • apps/webapp/app/models/projectAlert.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/v3/alertsWorker.server.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts
  • apps/webapp/app/v3/services/errorGroupActions.server.ts
  • internal-packages/clickhouse/src/errors.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/database/prisma/migrations/20260306102053_error_group_state/migration.sql
  • internal-packages/database/prisma/migrations/20260308181657_add_error_alert_config_to_project_alert_channel/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/emails/emails/alert-error-group.tsx
  • internal-packages/emails/src/index.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/webapp/app/components/logs/LogsVersionFilter.tsx
  • apps/webapp/app/components/runs/v3/RunFilters.tsx
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • internal-packages/clickhouse/src/errors.ts

@matt-aitken matt-aitken force-pushed the errors-add-version-filter branch from 6875ac8 to 497f301 Compare March 20, 2026 15:05
Copy link
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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (3)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx (1)

119-125: ⚠️ Potential issue | 🟠 Major

Apply versions to the activity query as well.

This only filters the list query. The occurrencesPromise below still calls presenter.getOccurrences() without versions, so after selecting a version the counts change but the Activity sparkline still shows all versions. Please thread the same filter into that query too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
around lines 119 - 125, The activity counts query is not receiving the versions
filter — mirror how you pass versions into the list query by including versions
(only when versions.length > 0 ? versions : undefined) in the call to
presenter.getOccurrences for occurrencesPromise; update the occurrencesPromise
invocation (presenter.getOccurrences) to accept the same parameters used for
listPromise (project.organizationId, environment.id, { userId, projectId:
project.id, tasks: tasks.length > 0 ? tasks : undefined, versions:
versions.length > 0 ? versions : undefined, statuses: statuses.length > 0 ?
statuses : undefined }) so the Activity sparkline respects the selected version
filter.
apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts (1)

273-299: ⚠️ Potential issue | 🟠 Major

Filter by status before computing cursors.

Pagination is already finalized before this block, then Lines 295-299 drop rows in memory. With a status filter applied, that can return short or empty pages even when more matching groups exist later, and the cursor can skip unseen matches. Filter before slicing/cursor generation, or over-fetch until you collect pageSize matching groups.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts` around lines 273
- 299, The transformedErrorGroups are filtered by statuses after
pagination/cursor logic which can produce short or skipped pages; change the
flow so you apply the status filter immediately after building
transformedErrorGroups (before any slicing/cursor generation) or implement
over-fetching: iterate the original errorGroups in order, map + filter by
statuses (using getGlobalSummary/getErrorGroupStates results) and collect until
you have pageSize matches (fetching more if needed) before creating cursors;
reference transformedErrorGroups, statuses, pageSize, getGlobalSummary and
getErrorGroupStates to locate where to apply the filter or implement the
over-fetch loop.
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts (1)

76-85: ⚠️ Potential issue | 🔴 Critical

getSummary() returns an object missing the required state field.

The return type ErrorGroupSummary (line 85) requires state: ErrorGroupState, but the object literal returned at lines 276–285 does not include it. This causes a TypeScript compilation error. While call() decorates the summary with state afterwards, getSummary() must satisfy its declared return type contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts` around lines 76
- 85, getSummary() currently returns an object missing the required state field
declared on the ErrorGroupSummary type; update getSummary() to include a proper
state property (of type ErrorGroupState) in the returned summary object (or
compute a default/derived state inside getSummary()) so its return satisfies the
ErrorGroupSummary contract, and ensure call() continues to decorate or override
state if needed; reference getSummary(), ErrorGroupSummary and call() when
making the change.
♻️ Duplicate comments (3)
apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts (1)

104-113: ⚠️ Potential issue | 🟡 Minor

Job deduplication may prevent immediate evaluation.

The stable evaluateErrorAlerts:${projectId} ID is shared with the evaluator's delayed self-chain. If a future evaluation job is already queued (e.g., scheduled to run in a few minutes), this enqueue becomes a no-op, and newly enabled ERROR_GROUP channels won't be evaluated until the existing job runs.

Consider using a unique ID for immediate kicks to ensure prompt evaluation after channel creation/update:

Suggested fix
  async `#scheduleErrorAlertEvaluation`(projectId: string): Promise<void> {
    await alertsWorker.enqueue({
-     id: `evaluateErrorAlerts:${projectId}`,
+     id: `evaluateErrorAlerts:immediate:${projectId}:${Date.now()}`,
      job: "v3.evaluateErrorAlerts",
      payload: {
        projectId,
        scheduledAt: Date.now(),
      },
    });
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts` around lines
104 - 113, The current `#scheduleErrorAlertEvaluation` uses a stable job id
`evaluateErrorAlerts:${projectId}` which can be deduplicated by the worker queue
and become a no-op if a future evaluator job already exists; change the enqueue
id for this immediate kick to be unique (e.g., append a timestamp or UUID such
as `evaluateErrorAlerts:${projectId}:kick:${Date.now()}`) so the job always gets
scheduled immediately, or alternatively pass any supported "dedupe: false"
option if the queue library exposes it; update the alertsWorker.enqueue call in
`#scheduleErrorAlertEvaluation` accordingly.
apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts (1)

313-320: ⚠️ Potential issue | 🟠 Major

Avoid logging and including webhook URLs in error messages.

Webhook URLs may contain embedded credentials in the path or query string. Logging the URL and including it in the thrown error exposes these secrets in worker logs and retry metadata.

Suggested fix
    if (!response.ok) {
      logger.info("[DeliverErrorGroupAlert] Failed to send webhook", {
        status: response.status,
        statusText: response.statusText,
-       url: webhookProperties.data.url,
+       channelId: payload.channelId,
      });
-     throw new Error(`Failed to send error group alert webhook to ${webhookProperties.data.url}`);
+     throw new Error("Failed to send error group alert webhook");
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts` around
lines 313 - 320, The code logs and throws an error that includes the raw webhook
URL (webhookProperties.data.url) which may expose credentials; update the
failure handling in deliverErrorGroupAlert (the block using logger.info,
response, and webhookProperties) to stop logging or throwing the full URL:
remove webhookProperties.data.url from logger.info payload and from the thrown
Error, instead log only non-sensitive metadata (e.g., response.status,
response.statusText, webhook id or sanitized hostname via new URL(...).host) and
throw an Error with a generic message (e.g., "Failed to send error group alert
webhook" or include only webhookProperties.id), ensuring no unmasked URL or
query/path credentials are emitted.
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx (1)

215-245: ⚠️ Potential issue | 🟠 Major

The page is still fingerprint-scoped, but the new actions are task-scoped.

ErrorGroupActions identifies state by taskIdentifier + errorFingerprint, yet this loader still fetches summary, activity, and runs by fingerprint alone. If the same fingerprint exists under multiple tasks, the page can render aggregate data for all of them and then resolve/ignore only whichever taskIdentifier happened to come back in the summary. Thread taskIdentifier through the route and presenter queries before exposing these buttons.

Also applies to: 415-419

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
around lines 215 - 245, The loader is fetching summary, activity and runs by
fingerprint only while ErrorGroupActions uses task-scoped identifiers
(taskIdentifier + errorFingerprint); update the route loader to accept and
forward the taskIdentifier and pass it into the presenter queries (e.g., the
call(...) invocation that returns detailPromise, presenter.getOccurrences(...),
and any presenter.getRuns/getSummary methods used later) so the presenter
queries use both taskIdentifier and fingerprint (or a single task-scoped
identifier) instead of fingerprint-alone; ensure the route params and any
timeFilterFromTo usage thread taskIdentifier through to presenter methods before
rendering/exposing ErrorGroupActions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx:
- Around line 80-98: The actionSchema currently allows empty taskIdentifier and
non-positive/zero ignore thresholds; update the discriminated union to enforce
taskIdentifier as a non-empty string (e.g., min length 1) and make duration,
occurrenceRate, and totalOccurrences coerce to numbers and require them to be
positive (e.g., .min(1) or .positive()) so empty/zero/negative values are
rejected server-side; apply these validators to the relevant branches in
actionSchema (the "resolve", "ignore", and "unresolve" objects) so
taskIdentifier, duration, occurrenceRate, and totalOccurrences are validated
before persisting.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx:
- Around line 81-150: The action creates/replaces channels via
CreateAlertChannelService.call using CreateAlertChannelOptions but never
persists the alert configuration (errorAlertConfig), so channels end up with
null configs; update the route to include the validated errorAlertConfig in the
options passed to CreateAlertChannelService.call (for email, slack and webhook
flows) and ensure CreateAlertChannelService
(apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts) writes that
errorAlertConfig to the DB on both create and update paths; reference the
CreateAlertChannelOptions type, CreateAlertChannelService.call invocations, and
the errorAlertConfig field when adding the config to the options before calling
the service and before deleting/replacing existing channels.

---

Outside diff comments:
In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts`:
- Around line 76-85: getSummary() currently returns an object missing the
required state field declared on the ErrorGroupSummary type; update getSummary()
to include a proper state property (of type ErrorGroupState) in the returned
summary object (or compute a default/derived state inside getSummary()) so its
return satisfies the ErrorGroupSummary contract, and ensure call() continues to
decorate or override state if needed; reference getSummary(), ErrorGroupSummary
and call() when making the change.

In `@apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts`:
- Around line 273-299: The transformedErrorGroups are filtered by statuses after
pagination/cursor logic which can produce short or skipped pages; change the
flow so you apply the status filter immediately after building
transformedErrorGroups (before any slicing/cursor generation) or implement
over-fetching: iterate the original errorGroups in order, map + filter by
statuses (using getGlobalSummary/getErrorGroupStates results) and collect until
you have pageSize matches (fetching more if needed) before creating cursors;
reference transformedErrorGroups, statuses, pageSize, getGlobalSummary and
getErrorGroupStates to locate where to apply the filter or implement the
over-fetch loop.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx:
- Around line 119-125: The activity counts query is not receiving the versions
filter — mirror how you pass versions into the list query by including versions
(only when versions.length > 0 ? versions : undefined) in the call to
presenter.getOccurrences for occurrencesPromise; update the occurrencesPromise
invocation (presenter.getOccurrences) to accept the same parameters used for
listPromise (project.organizationId, environment.id, { userId, projectId:
project.id, tasks: tasks.length > 0 ? tasks : undefined, versions:
versions.length > 0 ? versions : undefined, statuses: statuses.length > 0 ?
statuses : undefined }) so the Activity sparkline respects the selected version
filter.

---

Duplicate comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx:
- Around line 215-245: The loader is fetching summary, activity and runs by
fingerprint only while ErrorGroupActions uses task-scoped identifiers
(taskIdentifier + errorFingerprint); update the route loader to accept and
forward the taskIdentifier and pass it into the presenter queries (e.g., the
call(...) invocation that returns detailPromise, presenter.getOccurrences(...),
and any presenter.getRuns/getSummary methods used later) so the presenter
queries use both taskIdentifier and fingerprint (or a single task-scoped
identifier) instead of fingerprint-alone; ensure the route params and any
timeFilterFromTo usage thread taskIdentifier through to presenter methods before
rendering/exposing ErrorGroupActions.

In `@apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts`:
- Around line 104-113: The current `#scheduleErrorAlertEvaluation` uses a stable
job id `evaluateErrorAlerts:${projectId}` which can be deduplicated by the
worker queue and become a no-op if a future evaluator job already exists; change
the enqueue id for this immediate kick to be unique (e.g., append a timestamp or
UUID such as `evaluateErrorAlerts:${projectId}:kick:${Date.now()}`) so the job
always gets scheduled immediately, or alternatively pass any supported "dedupe:
false" option if the queue library exposes it; update the alertsWorker.enqueue
call in `#scheduleErrorAlertEvaluation` accordingly.

In `@apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts`:
- Around line 313-320: The code logs and throws an error that includes the raw
webhook URL (webhookProperties.data.url) which may expose credentials; update
the failure handling in deliverErrorGroupAlert (the block using logger.info,
response, and webhookProperties) to stop logging or throwing the full URL:
remove webhookProperties.data.url from logger.info payload and from the thrown
Error, instead log only non-sensitive metadata (e.g., response.status,
response.statusText, webhook id or sanitized hostname via new URL(...).host) and
throw an Error with a generic message (e.g., "Failed to send error group alert
webhook" or include only webhookProperties.id), ensuring no unmasked URL or
query/path credentials are emitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a52379f0-9e18-402e-b980-5af54bcd3984

📥 Commits

Reviewing files that changed from the base of the PR and between e609e8b and 91ab04b.

📒 Files selected for processing (27)
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/components/logs/LogsVersionFilter.tsx
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • apps/webapp/app/components/runs/v3/RunFilters.tsx
  • apps/webapp/app/models/projectAlert.server.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/alertsWorker.server.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts
  • apps/webapp/app/v3/services/errorGroupActions.server.ts
  • internal-packages/clickhouse/src/errors.ts
  • internal-packages/clickhouse/src/index.ts
  • internal-packages/database/prisma/migrations/20260306102053_error_group_state/migration.sql
  • internal-packages/database/prisma/migrations/20260308181657_add_error_alert_config_to_project_alert_channel/migration.sql
  • internal-packages/database/prisma/migrations/20260320115950_add_ignored_at_occurrence_count_to_error_group_state/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/emails/emails/alert-error-group.tsx
  • internal-packages/emails/src/index.tsx
✅ Files skipped from review due to trivial changes (7)
  • internal-packages/database/prisma/migrations/20260320115950_add_ignored_at_occurrence_count_to_error_group_state/migration.sql
  • internal-packages/database/prisma/migrations/20260308181657_add_error_alert_config_to_project_alert_channel/migration.sql
  • apps/webapp/app/components/runs/v3/RunFilters.tsx
  • apps/webapp/app/components/logs/LogsVersionFilter.tsx
  • apps/webapp/app/v3/services/errorGroupActions.server.ts
  • internal-packages/database/prisma/migrations/20260306102053_error_group_state/migration.sql
  • apps/webapp/app/v3/alertsWorker.server.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/webapp/app/models/projectAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
  • internal-packages/emails/src/index.tsx
  • internal-packages/clickhouse/src/index.ts
  • apps/webapp/app/components/primitives/charts/ChartRoot.tsx
  • apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts
  • internal-packages/emails/emails/alert-error-group.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{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

**/*.{ts,tsx}: Use task export syntax: export const myTask = task({ id: 'my-task', run: async (payload) => { ... } })
Use Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work - avoid DEPRECATED zodworker (Graphile-worker wrapper)
Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • internal-packages/clickhouse/src/errors.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.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/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • internal-packages/clickhouse/src/errors.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.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/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • internal-packages/clickhouse/src/errors.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • internal-packages/clickhouse/src/errors.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/{webapp,supervisor}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Always import from @trigger.dev/sdk for Trigger.dev tasks - never use @trigger.dev/sdk/v3 or deprecated client.defineJob
Import subpaths only from @trigger.dev/core, never import from root
Add crumbs as you write code using // @crumbs comments or // #region @crumbs blocks for agentcrumbs debug tracing

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • internal-packages/clickhouse/src/errors.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/webapp/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Files:

  • apps/webapp/app/utils/pathBuilder.ts
  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}

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

Organize services in the webapp following the pattern app/v3/services/*/*.server.ts

Files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
apps/webapp/**/*.server.ts

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

Access environment variables via the env export from app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/webapp/app/v3/services/**/*.server.ts

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

When editing services that branch on RunEngineVersion to support both V1 and V2 (e.g., cancelTaskRun.server.ts, batchTriggerV3.server.ts), only modify V2 code paths

Files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
apps/webapp/app/v3/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying V3 code paths in apps/webapp/app/v3/, only modify V2 code - consult apps/webapp/CLAUDE.md for V1-only legacy code to avoid

Files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
apps/webapp/app/routes/**/*.ts

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

Use Remix flat-file route convention with dot-separated segments (e.g., api.v1.tasks.$taskId.trigger.ts for /api/v1/tasks/:taskId/trigger)

Files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
🧠 Learnings (36)
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to apps/webapp/app/v3/**/*.{ts,tsx} : When modifying V3 code paths in apps/webapp/app/v3/, only modify V2 code - consult apps/webapp/CLAUDE.md for V1-only legacy code to avoid

Applied to files:

  • apps/webapp/app/utils/pathBuilder.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/v3/*Worker.server.ts : New background job workers should use `trigger.dev/redis-worker` (e.g., `commonWorker.server.ts`, `alertsWorker.server.ts`, `batchTriggerWorker.server.ts`), not zodworker or graphile-worker

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach cron schedules declaratively using the `cron` property or imperatively using `schedules.create()`

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `idempotencyKeys.create()` to create idempotency keys for preventing duplicate task executions

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/**/*.server.ts : Access environment variables via the `env` export from `app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/**/*.test.{ts,tsx} : In test files, do not import `env.server.ts` directly; pass configuration as constructor arguments or options instead for testable code

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
📚 Learning: 2026-03-10T17:56:20.938Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3201
File: apps/webapp/app/v3/services/setSeatsAddOn.server.ts:25-29
Timestamp: 2026-03-10T17:56:20.938Z
Learning: Do not implement local userId-to-organizationId authorization checks inside org-scoped service classes (e.g., SetSeatsAddOnService, SetBranchesAddOnService) in the web app. Rely on route-layer authentication (requireUserId(request)) and org membership enforcement via the _app.orgs.$organizationSlug layout route. Any userId/organizationId that reaches these services from org-scoped routes has already been validated. Apply this pattern across all org-scoped services to avoid redundant auth checks and maintain consistency.

Applied to files:

  • apps/webapp/app/v3/services/alerts/createAlertChannel.server.ts
  • apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to apps/webapp/**/*.{ts,tsx,jsx,js} : Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/routes/**/*.ts : Use Remix flat-file route convention with dot-separated segments (e.g., `api.v1.tasks.$taskId.trigger.ts` for `/api/v1/tasks/:taskId/trigger`)

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
📚 Learning: 2026-02-11T16:50:14.167Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx:126-131
Timestamp: 2026-02-11T16:50:14.167Z
Learning: In apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx, MetricsDashboard entities are intentionally scoped to the organization level, not the project level. The dashboard lookup should filter by organizationId only (not projectId), allowing dashboards to be accessed across projects within the same organization. The optional projectId field on MetricsDashboard serves other purposes and should not be used as an authorization constraint.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-03-13T13:45:39.411Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3213
File: apps/webapp/app/routes/admin.llm-models.missing.$model.tsx:19-21
Timestamp: 2026-03-13T13:45:39.411Z
Learning: In `apps/webapp/app/routes/admin.llm-models.missing.$model.tsx`, the `decodeURIComponent(params.model ?? "")` call is intentionally unguarded. Remix route params are decoded at the routing layer before reaching the loader, so malformed percent-encoding is rejected upstream. The page is also admin-only, so the risk is minimal and no try-catch is warranted.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
📚 Learning: 2026-02-03T18:27:49.039Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:49.039Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (like the Edit button with PencilSquareIcon) intentionally have no text labels - only icons are shown in the TableCellMenu. This is a deliberate UI design pattern for compact icon-only menu items.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-10T16:18:48.654Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2980
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx:512-515
Timestamp: 2026-02-10T16:18:48.654Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx, environment.queueSizeLimit is a per-queue maximum that is configured at the environment level, not a shared limit across all queues. Each queue can have up to environment.queueSizeLimit items queued independently.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
📚 Learning: 2026-02-06T19:53:38.843Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts:233-237
Timestamp: 2026-02-06T19:53:38.843Z
Learning: When constructing Vercel dashboard URLs from deployment IDs, always strip the dpl_ prefix from the ID. Implement this by transforming the ID with .replace(/^dpl_/, "") before concatenating into the URL: https://vercel.com/${teamSlug}/${projectName}/${cleanedDeploymentId}. Consider centralizing this logic in a small helper (e.g., getVercelDeploymentId(id) or a URL builder) and add tests to verify both prefixed and non-prefixed inputs.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-01-28T14:15:15.011Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2953
File: apps/webapp/app/components/runs/v3/SharedFilters.tsx:441-452
Timestamp: 2026-01-28T14:15:15.011Z
Learning: In apps/webapp/app/components/runs/v3/SharedFilters.tsx, the maxPeriodDays limit for date ranges should only check the from date (via dateRangeToDays(fromValue)) because it enforces data retention limits—how far back in history queries can reach. The to date is irrelevant for retention-based limits.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to **/*.{ts,tsx} : Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client

Applied to files:

  • internal-packages/database/prisma/schema.prisma
  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.ts : When editing services that branch on `RunEngineVersion` to support both V1 and V2 (e.g., `cancelTaskRun.server.ts`, `batchTriggerV3.server.ts`), only modify V2 code paths

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-06-14T08:07:46.625Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2175
File: apps/webapp/app/services/environmentMetricsRepository.server.ts:202-207
Timestamp: 2025-06-14T08:07:46.625Z
Learning: In apps/webapp/app/services/environmentMetricsRepository.server.ts, the ClickHouse methods (getTaskActivity, getCurrentRunningStats, getAverageDurations) intentionally do not filter by the `tasks` parameter at the ClickHouse level, even though the tasks parameter is accepted by the public methods. This is done on purpose as there is not much benefit from adding that filtering at the ClickHouse layer.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : 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)

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-03-04T16:40:11.313Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3172
File: apps/webapp/app/utils/semver.ts:19-33
Timestamp: 2026-03-04T16:40:11.313Z
Learning: In `apps/webapp/app/utils/semver.ts`, the versioning scheme used is date-based (e.g. `20260304.1`, `20260304.2`), not semantic versioning. Pre-release suffix handling (e.g. `-beta`, `-rc`) is not a concern for this file.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Leverage the PostgreSQL database through the `trigger.dev/database` Prisma 6.14.0 client in the webapp for all data access patterns

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts

Comment on lines +80 to +98
const actionSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("resolve"),
taskIdentifier: z.string(),
resolvedInVersion: z.string().optional(),
}),
z.object({
action: z.literal("ignore"),
taskIdentifier: z.string(),
duration: z.coerce.number().optional(),
occurrenceRate: z.coerce.number().optional(),
totalOccurrences: z.coerce.number().optional(),
reason: z.string().optional(),
}),
z.object({
action: z.literal("unresolve"),
taskIdentifier: z.string(),
}),
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant section
fd "route.tsx" apps/webapp/app/routes --exec wc -l {} \; | head -5

Repository: triggerdotdev/trigger.dev

Length of output: 382


🏁 Script executed:

# Try to find and read the file
git ls-files "apps/webapp/app/routes/*errors*" | head -10

Repository: triggerdotdev/trigger.dev

Length of output: 518


🏁 Script executed:

# Search for the error fingerprint route
rg "errorGroup.taskIdentifier|ErrorGroupState" apps/webapp --type ts --type tsx -A 2 -B 2 | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Read the target file
cat -n "apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx" | head -110 | tail -40

Repository: triggerdotdev/trigger.dev

Length of output: 167


🏁 Script executed:

# Search for ErrorGroupState and taskIdentifier usage
rg "ErrorGroupState|errorGroup\.taskIdentifier" apps/webapp -A 3 -B 1

Repository: triggerdotdev/trigger.dev

Length of output: 12400


🏁 Script executed:

# Check for database schema or type definitions related to ErrorGroupState
fd "ErrorGroupState\|errorGroup" apps/webapp --extension ts --extension tsx | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Read the route file using a different method
file_path="apps/webapp/app/routes/_app.orgs.\$organizationSlug.projects.\$projectParam.env.\$envParam.errors.\$fingerprint/route.tsx"
if [ -f "$file_path" ]; then
  sed -n '75,105p' "$file_path"
else
  echo "File not found at: $file_path"
  # Try listing the actual files
  find apps/webapp/app/routes -name "route.tsx" -path "*errors*fingerprint*" -type f
fi

Repository: triggerdotdev/trigger.dev

Length of output: 950


🏁 Script executed:

# Look at the Prisma schema for errorGroupState
rg "errorGroupState" --type ts --type prisma apps/ -B 2 -A 5 | grep -E "(model errorGroupState|taskIdentifier|field|@db)" | head -30

Repository: triggerdotdev/trigger.dev

Length of output: 99


🏁 Script executed:

# Find and inspect the Prisma schema file
fd "schema.prisma" apps/ internal-packages/

Repository: triggerdotdev/trigger.dev

Length of output: 116


🏁 Script executed:

# Search for errorGroupState model in Prisma schema
rg "model errorGroupState" internal-packages/database/prisma/schema.prisma -A 20

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Also search for any validation or constraints on taskIdentifier
rg "taskIdentifier" internal-packages/database/prisma/schema.prisma -B 3 -A 3

Repository: triggerdotdev/trigger.dev

Length of output: 2515


🏁 Script executed:

# Read the action handler to see how the data is persisted
sed -n '100,200p' "apps/webapp/app/routes/_app.orgs.\$organizationSlug.projects.\$projectParam.env.\$envParam.errors.\$fingerprint/route.tsx" | head -80

Repository: triggerdotdev/trigger.dev

Length of output: 2799


🏁 Script executed:

# Look for where errorGroupState is updated
rg "errorGroupState.*update|errorGroupState.*create" apps/webapp --type ts --type tsx -A 5 | head -60

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Check the full Prisma schema for errorGroupState to see all fields
cat internal-packages/database/prisma/schema.prisma | grep -A 30 "model errorGroupState"

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Search for ErrorGroupActions class
rg "class ErrorGroupActions|export.*ErrorGroupActions" apps/webapp --type ts --type tsx -A 2 | head -40

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Find the file with ErrorGroupActions
fd "ErrorGroupActions" apps/webapp --type ts --type tsx

Repository: triggerdotdev/trigger.dev

Length of output: 238


🏁 Script executed:

# Try to get the errorGroupState model from Prisma schema
sed -n '/^model errorGroupState/,/^}/p' internal-packages/database/prisma/schema.prisma

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Find ErrorGroupActions
rg "ErrorGroupActions" apps/webapp -l

Repository: triggerdotdev/trigger.dev

Length of output: 242


🏁 Script executed:

# Read the entire errorGroupState model definition
cat internal-packages/database/prisma/schema.prisma | grep -A 25 "^model errorGroupState"

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Search for where errorGroupState is created/updated in the webapp
rg "errorGroupState\.(create|update)" apps/webapp -B 2 -A 5

Repository: triggerdotdev/trigger.dev

Length of output: 832


🏁 Script executed:

# Read the ErrorGroupActions service
wc -l apps/webapp/app/v3/services/errorGroupActions.server.ts

Repository: triggerdotdev/trigger.dev

Length of output: 128


🏁 Script executed:

# Read key methods from ErrorGroupActions
cat apps/webapp/app/v3/services/errorGroupActions.server.ts | head -300

Repository: triggerdotdev/trigger.dev

Length of output: 4328


Validate non-empty identifiers and positive ignore thresholds server-side.

z.string() accepts empty strings, and z.coerce.number() accepts zero or negative values. These can be persisted directly to the database without constraints, allowing crafted POSTs to create errorGroupState entries with empty taskIdentifier or invalid ignore conditions despite client-side min={1} attributes.

🔧 Tighten the action schema
 const actionSchema = z.discriminatedUnion("action", [
   z.object({
     action: z.literal("resolve"),
-    taskIdentifier: z.string(),
+    taskIdentifier: z.string().trim().min(1),
     resolvedInVersion: z.string().optional(),
   }),
   z.object({
     action: z.literal("ignore"),
-    taskIdentifier: z.string(),
-    duration: z.coerce.number().optional(),
-    occurrenceRate: z.coerce.number().optional(),
-    totalOccurrences: z.coerce.number().optional(),
+    taskIdentifier: z.string().trim().min(1),
+    duration: z.coerce.number().positive().optional(),
+    occurrenceRate: z.coerce.number().positive().optional(),
+    totalOccurrences: z.coerce.number().positive().optional(),
     reason: z.string().optional(),
   }),
   z.object({
     action: z.literal("unresolve"),
-    taskIdentifier: z.string(),
+    taskIdentifier: z.string().trim().min(1),
   }),
 ]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const actionSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("resolve"),
taskIdentifier: z.string(),
resolvedInVersion: z.string().optional(),
}),
z.object({
action: z.literal("ignore"),
taskIdentifier: z.string(),
duration: z.coerce.number().optional(),
occurrenceRate: z.coerce.number().optional(),
totalOccurrences: z.coerce.number().optional(),
reason: z.string().optional(),
}),
z.object({
action: z.literal("unresolve"),
taskIdentifier: z.string(),
}),
]);
const actionSchema = z.discriminatedUnion("action", [
z.object({
action: z.literal("resolve"),
taskIdentifier: z.string().trim().min(1),
resolvedInVersion: z.string().optional(),
}),
z.object({
action: z.literal("ignore"),
taskIdentifier: z.string().trim().min(1),
duration: z.coerce.number().positive().optional(),
occurrenceRate: z.coerce.number().positive().optional(),
totalOccurrences: z.coerce.number().positive().optional(),
reason: z.string().optional(),
}),
z.object({
action: z.literal("unresolve"),
taskIdentifier: z.string().trim().min(1),
}),
]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
around lines 80 - 98, The actionSchema currently allows empty taskIdentifier and
non-positive/zero ignore thresholds; update the discriminated union to enforce
taskIdentifier as a non-empty string (e.g., min length 1) and make duration,
occurrenceRate, and totalOccurrences coerce to numbers and require them to be
positive (e.g., .min(1) or .positive()) so empty/zero/negative values are
rejected server-side; apply these validators to the relevant branches in
actionSchema (the "resolve", "ignore", and "unresolve" objects) so
taskIdentifier, duration, occurrenceRate, and totalOccurrences are validated
before persisting.

Copy link
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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx (1)

430-437: ⚠️ Potential issue | 🟡 Minor

Preserve alerts when clearing filters.

These empty GET forms wipe the whole query string, so clicking “Clear all filters” also removes alerts=true and closes the side panel. Keep non-filter params like alerts in hidden inputs, or clear only the known filter keys.

Also applies to: 447-454

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
around lines 430 - 437, The clear-filters Form currently issues an empty GET
which clears the entire query string including non-filter params like alerts;
update the Form(s) rendered when hasFilters (and the similar Form at the 447-454
block) to preserve alerts by adding a hidden input named "alerts" with its
current value when present OR change the clear action to only remove known
filter keys (e.g., explicitly submit only filter keys to clear) so that the
alerts=true query param (and other non-filter params) are not removed when the
Button (variant secondary/small) is clicked.
🧹 Nitpick comments (1)
apps/webapp/app/components/primitives/UnorderedList.tsx (1)

107-129: Add required crumbs tracing comments in this new TSX component.

Please add // @Crumbs (or a `// `#region` `@crumbs block) around the new component logic so it follows the repo tracing convention.

Suggested minimal update
+// `#region` `@crumbs` UnorderedList primitive
 export function UnorderedList({
   variant = "base",
   className,
   spacing = false,
   children,
   ...props
 }: UnorderedListProps) {
   const v = listVariants[variant];
   return (
     <ul
       className={cn(
         "list-none [&>li]:flex [&>li]:items-baseline [&>li]:before:shrink-0 [&>li]:before:content-['•']",
         v.text,
         v.items,
         spacing && v.spacing,
         className
       )}
       {...props}
     >
       {children}
     </ul>
   );
 }
+// `#endregion` `@crumbs`

As per coding guidelines "Add crumbs as you write code using // @crumbs comments or // #region @crumbs blocks for agentcrumbs debug tracing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/components/primitives/UnorderedList.tsx` around lines 107 -
129, Wrap the UnorderedList component logic with the repo's crumbs tracing
comments: add a leading `// `@crumbs`` (or `// `#region` `@crumbs``) immediately above
the UnorderedList function declaration and a closing `// `#endregion` `@crumbs`` (if
using region) after the component return/closing brace; ensure the markers
enclose the import/usage of listVariants and cn within the same block so the
entire component (function UnorderedList, usage of listVariants, cn, props) is
covered by the crumbs tracing convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx:
- Around line 99-103: The route parses and forwards the statuses array but the
presenter (ErrorsListPresenter.server.ts) only applies a versions predicate, so
the status dropdown changes URL/UI but not results; update ErrorsListPresenter
(where predicates are composed—e.g., the function building the query/predicates
or the applyFilters method) to add a predicate that filters ErrorGroup.status
(or the equivalent field) by the incoming statuses array (values
"UNRESOLVED"|"RESOLVED"|"IGNORED"), ensure you skip adding the predicate when
statuses is empty, and include the statuses variable/name used in the route so
the presenter reads and applies that filter alongside versions.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx:
- Around line 84-149: The current deletion logic removes any existing
ERROR_GROUP channels not re-created in this request (using existingChannels,
processedChannelIds), which deletes hidden/unchanged types; fix by determining
which channel types were actually editable in this submission (e.g., email when
emailAlertsEnabled, slack when slack integration is READY, webhook if webhooks
section present) and only consider those types for deletion — either 1) before
filtering, add the IDs of existing channels whose channel.type is NOT editable
to processedChannelIds so they are preserved, or 2) change channelsToDelete to
filter only channels whose channel.type is in the set of edited types; update
the loop that builds processedChannelIds (and the deletion filter that uses
existingChannels and channelsToDelete) accordingly, referencing
CreateAlertChannelService, processedChannelIds, existingChannels, and
channelsToDelete.

---

Outside diff comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx:
- Around line 430-437: The clear-filters Form currently issues an empty GET
which clears the entire query string including non-filter params like alerts;
update the Form(s) rendered when hasFilters (and the similar Form at the 447-454
block) to preserve alerts by adding a hidden input named "alerts" with its
current value when present OR change the clear action to only remove known
filter keys (e.g., explicitly submit only filter keys to clear) so that the
alerts=true query param (and other non-filter params) are not removed when the
Button (variant secondary/small) is clicked.

---

Nitpick comments:
In `@apps/webapp/app/components/primitives/UnorderedList.tsx`:
- Around line 107-129: Wrap the UnorderedList component logic with the repo's
crumbs tracing comments: add a leading `// `@crumbs`` (or `// `#region` `@crumbs``)
immediately above the UnorderedList function declaration and a closing `//
`#endregion` `@crumbs`` (if using region) after the component return/closing brace;
ensure the markers enclose the import/usage of listVariants and cn within the
same block so the entire component (function UnorderedList, usage of
listVariants, cn, props) is covered by the crumbs tracing convention.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 086aa7a7-8789-4184-851f-e33d6e7c22e9

📥 Commits

Reviewing files that changed from the base of the PR and between 91ab04b and e91c887.

📒 Files selected for processing (7)
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/storybook.unordered-list/route.tsx
  • apps/webapp/app/routes/storybook/route.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/webapp/app/routes/storybook/route.tsx
  • apps/webapp/app/routes/storybook.unordered-list/route.tsx
📜 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). (28)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: typecheck / typecheck
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{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

**/*.{ts,tsx}: Use task export syntax: export const myTask = task({ id: 'my-task', run: async (payload) => { ... } })
Use Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work - avoid DEPRECATED zodworker (Graphile-worker wrapper)
Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
{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/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
apps/{webapp,supervisor}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Always import from @trigger.dev/sdk for Trigger.dev tasks - never use @trigger.dev/sdk/v3 or deprecated client.defineJob
Import subpaths only from @trigger.dev/core, never import from root
Add crumbs as you write code using // @crumbs comments or // #region @crumbs blocks for agentcrumbs debug tracing

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
apps/webapp/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
🧠 Learnings (14)
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to apps/webapp/**/*.{ts,tsx,jsx,js} : Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Applied to files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/components/layout/AppLayout.tsx
  • apps/webapp/app/components/primitives/UnorderedList.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
📚 Learning: 2026-03-13T13:45:39.411Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3213
File: apps/webapp/app/routes/admin.llm-models.missing.$model.tsx:19-21
Timestamp: 2026-03-13T13:45:39.411Z
Learning: In `apps/webapp/app/routes/admin.llm-models.missing.$model.tsx`, the `decodeURIComponent(params.model ?? "")` call is intentionally unguarded. Remix route params are decoded at the routing layer before reaching the loader, so malformed percent-encoding is rejected upstream. The page is also admin-only, so the risk is minimal and no try-catch is warranted.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/routes/**/*.ts : Use Remix flat-file route convention with dot-separated segments (e.g., `api.v1.tasks.$taskId.trigger.ts` for `/api/v1/tasks/:taskId/trigger`)

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
📚 Learning: 2026-02-11T16:50:14.167Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx:126-131
Timestamp: 2026-02-11T16:50:14.167Z
Learning: In apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx, MetricsDashboard entities are intentionally scoped to the organization level, not the project level. The dashboard lookup should filter by organizationId only (not projectId), allowing dashboards to be accessed across projects within the same organization. The optional projectId field on MetricsDashboard serves other purposes and should not be used as an authorization constraint.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2026-02-10T16:18:48.654Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2980
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx:512-515
Timestamp: 2026-02-10T16:18:48.654Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx, environment.queueSizeLimit is a per-queue maximum that is configured at the environment level, not a shared limit across all queues. Each queue can have up to environment.queueSizeLimit items queued independently.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
🔇 Additional comments (3)
apps/webapp/app/components/layout/AppLayout.tsx (1)

23-34: LGTM!

The addition of the optional className prop follows the established pattern used by other layout components in this file (AppContainer, PageBody, etc.) and correctly uses the cn utility for class merging.

apps/webapp/app/components/primitives/UnorderedList.tsx (1)

4-129: Solid primitive component implementation.

Record<ParagraphVariant, ...> on Line 4 plus the typed props and function declaration on Line 107 make this addition safe and maintainable. Nice work.

apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx (1)

32-36: Nice normalization for repeatable fields.

Accepting both a single value and repeated inputs as arrays keeps the action path simple and prevents the trailing blank row from being persisted.

Also applies to: 39-43

Comment on lines +84 to +149
const existingChannels = await prisma.projectAlertChannel.findMany({
where: {
projectId: project.id,
alertTypes: { has: "ERROR_GROUP" },
environmentTypes: { has: environment.type },
},
});

const service = new CreateAlertChannelService();
const environmentTypes = [environment.type];
const processedChannelIds = new Set<string>();

for (const email of emails) {
const options: CreateAlertChannelOptions = {
name: `Error alert to ${email}`,
alertTypes: ["ERROR_GROUP"],
environmentTypes,
deduplicationKey: `error-email:${email}:${environment.type}`,
channel: { type: "EMAIL", email },
};
const channel = await service.call(project.externalRef, userId, options);
processedChannelIds.add(channel.id);
}

if (slackChannel) {
const [channelId, channelName] = slackChannel.split("/");
if (channelId && channelName) {
const options: CreateAlertChannelOptions = {
name: `Error alert to #${channelName}`,
alertTypes: ["ERROR_GROUP"],
environmentTypes,
deduplicationKey: `error-slack:${environment.type}`,
channel: {
type: "SLACK",
channelId,
channelName,
integrationId: slackIntegrationId,
},
};
const channel = await service.call(project.externalRef, userId, options);
processedChannelIds.add(channel.id);
}
}

for (const url of webhooks) {
const options: CreateAlertChannelOptions = {
name: `Error alert to ${new URL(url).hostname}`,
alertTypes: ["ERROR_GROUP"],
environmentTypes,
deduplicationKey: `error-webhook:${url}:${environment.type}`,
channel: { type: "WEBHOOK", url },
};
const channel = await service.call(project.externalRef, userId, options);
processedChannelIds.add(channel.id);
}

const channelsToDelete = existingChannels.filter(
(ch) =>
!processedChannelIds.has(ch.id) &&
ch.alertTypes.length === 1 &&
ch.alertTypes[0] === "ERROR_GROUP"
);

for (const ch of channelsToDelete) {
await prisma.projectAlertChannel.delete({ where: { id: ch.id } });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unavailable sections are being treated as removals.

ConfigureErrorAlerts only posts email fields when emailAlertsEnabled is true and only posts Slack fields when Slack is READY. This delete-by-diff flow removes every existing ERROR_GROUP channel id that was not re-processed, so saving a webhook change while one of those sections is hidden will silently delete the stored email/Slack destinations. Only diff the channel types that were editable in this submission, or carry forward the hidden values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
around lines 84 - 149, The current deletion logic removes any existing
ERROR_GROUP channels not re-created in this request (using existingChannels,
processedChannelIds), which deletes hidden/unchanged types; fix by determining
which channel types were actually editable in this submission (e.g., email when
emailAlertsEnabled, slack when slack integration is READY, webhook if webhooks
section present) and only consider those types for deletion — either 1) before
filtering, add the IDs of existing channels whose channel.type is NOT editable
to processedChannelIds so they are preserved, or 2) change channelsToDelete to
filter only channels whose channel.type is in the set of edited types; update
the loop that builds processedChannelIds (and the deletion filter that uses
existingChannels and channelsToDelete) accordingly, referencing
CreateAlertChannelService, processedChannelIds, existingChannels, and
channelsToDelete.

Copy link
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)
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts (1)

79-89: ⚠️ Potential issue | 🔴 Critical

Initialize state in getSummary() now that it is required.

ErrorGroupSummary now requires state, but getSummary() still returns an object without that field. This breaks the file’s TypeScript contract before call() gets a chance to overwrite summary.state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts` around lines 79
- 89, getSummary() currently returns an object missing the required state field
of ErrorGroupSummary; update getSummary() to include a default state value
(e.g., ErrorGroupState.Open or the appropriate ErrorGroupState enum member) in
the returned summary object so the returned type satisfies ErrorGroupSummary
before call() may overwrite summary.state; locate the getSummary function in
ErrorGroupPresenter.server.ts and add the state property to the returned object
referencing ErrorGroupState.
♻️ Duplicate comments (2)
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts (1)

133-148: ⚠️ Potential issue | 🟠 Major

State lookup still needs taskIdentifier.

ErrorGroupState is keyed by (environmentId, taskIdentifier, errorFingerprint), but this code still fetches it with only (environmentId, errorFingerprint) and does so before summary.taskIdentifier is known. If the same fingerprint exists under multiple tasks, the page can surface the wrong resolved/ignored state. Fetch summary first, then look up state via the full composite key (findUnique fits here).

Also applies to: 324-332

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts` around lines 133
- 148, The state lookup is being called before the summary and without
taskIdentifier; change the flow in ErrorGroupPresenter.server.ts so you await
this.getSummary(...) first, extract summary.taskIdentifier, then fetch state
using the composite key (environmentId, taskIdentifier, fingerprint) — replace
the current this.getState(environmentId, fingerprint) call with a lookup that
uses the full composite key (use the repository's findUnique or equivalent to
query by environmentId, taskIdentifier, and errorFingerprint). Apply the same
fix to the other occurrence mentioned (lines around the second getState call).
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx (1)

80-98: ⚠️ Potential issue | 🟠 Major

Reject empty task identifiers and non-positive ignore thresholds server-side.

These branches still allow an empty taskIdentifier and zero/negative ignore thresholds through to ErrorGroupActions. That lets crafted POSTs create ErrorGroupState rows the UI cannot reliably match, or ignore conditions that never behave as intended. Tighten this schema with a trimmed non-empty identifier and positive numeric validators.

As per coding guidelines, "Use zod for validation in packages/core and apps/webapp".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
around lines 80 - 98, The discriminated union actionSchema allows empty
taskIdentifier and non-positive ignore thresholds; update the schema so
taskIdentifier uses a trimmed non-empty string (e.g., z.string().trim().min(1))
in every branch (resolve, ignore, unresolve) and make the numeric ignore fields
(duration, occurrenceRate, totalOccurrences) use coercion plus positive checks
(e.g., z.coerce.number().positive().optional()) so zero/negative values are
rejected; keep resolvedInVersion and reason optional but consider trimming them
as well for consistency.
🧹 Nitpick comments (1)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx (1)

820-845: Normalize dotted version strings used as chart series keys.

Version strings like 20260304.1 are used directly as object keys in the chart config (cfg[versions[i]]). While JavaScript bracket notation handles this correctly and recharts typically checks for direct properties before nested path traversal, this pattern is fragile. Recharts' accessor implementation could change, or future maintainers might not realize why this works. Normalize to internal series IDs (e.g., array indices) and use version strings only as labels:

cfg[i] = {
  label: versions[i],
  color: getSeriesColor(i),
}

Also applies to: 890-899

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
around lines 820 - 845, The chart currently uses dotted version strings as
object keys (cfg[versions[i]]) which can be fragile for recharts accessors; in
ActivityChart replace those string keys with numeric series IDs (use i as the
key) so cfg[i] = { label: versions[i], color: getSeriesColor(i) } and update the
data transformation so each activity point maps its series value to the same
numeric ID instead of the dotted version string; make sure to keep versions[i]
as the human-readable label and apply the same change to the other chart config
block that builds cfg (the second occurrence that also references cfg, versions,
getSeriesColor, and activity/data).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts`:
- Around line 79-89: getSummary() currently returns an object missing the
required state field of ErrorGroupSummary; update getSummary() to include a
default state value (e.g., ErrorGroupState.Open or the appropriate
ErrorGroupState enum member) in the returned summary object so the returned type
satisfies ErrorGroupSummary before call() may overwrite summary.state; locate
the getSummary function in ErrorGroupPresenter.server.ts and add the state
property to the returned object referencing ErrorGroupState.

---

Duplicate comments:
In `@apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts`:
- Around line 133-148: The state lookup is being called before the summary and
without taskIdentifier; change the flow in ErrorGroupPresenter.server.ts so you
await this.getSummary(...) first, extract summary.taskIdentifier, then fetch
state using the composite key (environmentId, taskIdentifier, fingerprint) —
replace the current this.getState(environmentId, fingerprint) call with a lookup
that uses the full composite key (use the repository's findUnique or equivalent
to query by environmentId, taskIdentifier, and errorFingerprint). Apply the same
fix to the other occurrence mentioned (lines around the second getState call).

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx:
- Around line 80-98: The discriminated union actionSchema allows empty
taskIdentifier and non-positive ignore thresholds; update the schema so
taskIdentifier uses a trimmed non-empty string (e.g., z.string().trim().min(1))
in every branch (resolve, ignore, unresolve) and make the numeric ignore fields
(duration, occurrenceRate, totalOccurrences) use coercion plus positive checks
(e.g., z.coerce.number().positive().optional()) so zero/negative values are
rejected; keep resolvedInVersion and reason optional but consider trimming them
as well for consistency.

---

Nitpick comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx:
- Around line 820-845: The chart currently uses dotted version strings as object
keys (cfg[versions[i]]) which can be fragile for recharts accessors; in
ActivityChart replace those string keys with numeric series IDs (use i as the
key) so cfg[i] = { label: versions[i], color: getSeriesColor(i) } and update the
data transformation so each activity point maps its series value to the same
numeric ID instead of the dotted version string; make sure to keep versions[i]
as the human-readable label and apply the same change to the other chart config
block that builds cfg (the second occurrence that also references cfg, versions,
getSeriesColor, and activity/data).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d9f30fbf-5e21-4716-a3ce-d6e0559ba25e

📥 Commits

Reviewing files that changed from the base of the PR and between e91c887 and dbe89dd.

📒 Files selected for processing (2)
  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📜 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). (16)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: typecheck / typecheck
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{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

**/*.{ts,tsx}: Use task export syntax: export const myTask = task({ id: 'my-task', run: async (payload) => { ... } })
Use Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work - avoid DEPRECATED zodworker (Graphile-worker wrapper)
Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
{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/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
**/*.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/presenters/v3/ErrorGroupPresenter.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
apps/webapp/**/*.server.ts

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

Access environment variables via the env export from app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
apps/{webapp,supervisor}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

When modifying only server components (apps/webapp/, apps/supervisor/) with no package changes, add a .server-changes/ file instead of a changeset

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Always import from @trigger.dev/sdk for Trigger.dev tasks - never use @trigger.dev/sdk/v3 or deprecated client.defineJob
Import subpaths only from @trigger.dev/core, never import from root
Add crumbs as you write code using // @crumbs comments or // #region @crumbs blocks for agentcrumbs debug tracing

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
apps/webapp/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
🧠 Learnings (22)
📚 Learning: 2026-01-08T15:57:09.323Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/otel-metrics.mdc:0-0
Timestamp: 2026-01-08T15:57:09.323Z
Learning: Applies to **/*.ts : 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)

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-03-04T16:40:11.313Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3172
File: apps/webapp/app/utils/semver.ts:19-33
Timestamp: 2026-03-04T16:40:11.313Z
Learning: In `apps/webapp/app/utils/semver.ts`, the versioning scheme used is date-based (e.g. `20260304.1`, `20260304.2`), not semantic versioning. Pre-release suffix handling (e.g. `-beta`, `-rc`) is not a concern for this file.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to **/*.{ts,tsx} : Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2025-07-12T18:06:04.133Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/services/runsRepository.server.ts:172-174
Timestamp: 2025-07-12T18:06:04.133Z
Learning: In apps/webapp/app/services/runsRepository.server.ts, the in-memory status filtering after fetching runs from Prisma is intentionally used as a workaround for ClickHouse data delays. This approach is acceptable because the result set is limited to a maximum of 100 runs due to pagination, making the performance impact negligible.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Leverage the PostgreSQL database through the `trigger.dev/database` Prisma 6.14.0 client in the webapp for all data access patterns

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-02-06T19:53:38.843Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/DeploymentListPresenter.server.ts:233-237
Timestamp: 2026-02-06T19:53:38.843Z
Learning: When constructing Vercel dashboard URLs from deployment IDs, always strip the dpl_ prefix from the ID. Implement this by transforming the ID with .replace(/^dpl_/, "") before concatenating into the URL: https://vercel.com/${teamSlug}/${projectName}/${cleanedDeploymentId}. Consider centralizing this logic in a small helper (e.g., getVercelDeploymentId(id) or a URL builder) and add tests to verify both prefixed and non-prefixed inputs.

Applied to files:

  • apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/routes/**/*.ts : Use Remix flat-file route convention with dot-separated segments (e.g., `api.v1.tasks.$taskId.trigger.ts` for `/api/v1/tasks/:taskId/trigger`)

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-04T16:34:48.876Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/vercel.connect.tsx:13-27
Timestamp: 2026-02-04T16:34:48.876Z
Learning: In apps/webapp/app/routes/vercel.connect.tsx, configurationId may be absent for "dashboard" flows but must be present for "marketplace" flows. Enforce this with a Zod superRefine and pass installationId to repository methods only when configurationId is defined (omit the field otherwise).

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-03-13T13:37:49.562Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.562Z
Learning: Applies to apps/webapp/**/*.{ts,tsx,jsx,js} : Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-03-13T13:45:39.411Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3213
File: apps/webapp/app/routes/admin.llm-models.missing.$model.tsx:19-21
Timestamp: 2026-03-13T13:45:39.411Z
Learning: In `apps/webapp/app/routes/admin.llm-models.missing.$model.tsx`, the `decodeURIComponent(params.model ?? "")` call is intentionally unguarded. Remix route params are decoded at the routing layer before reaching the loader, so malformed percent-encoding is rejected upstream. The page is also admin-only, so the risk is minimal and no try-catch is warranted.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-11T16:50:14.167Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx:126-131
Timestamp: 2026-02-11T16:50:14.167Z
Learning: In apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardId.widgets.tsx, MetricsDashboard entities are intentionally scoped to the organization level, not the project level. The dashboard lookup should filter by organizationId only (not projectId), allowing dashboards to be accessed across projects within the same organization. The optional projectId field on MetricsDashboard serves other purposes and should not be used as an authorization constraint.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-03-13T13:42:59.104Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3213
File: apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts:40-43
Timestamp: 2026-03-13T13:42:59.104Z
Learning: In `apps/webapp/app/routes/admin.api.v1.llm-models.$modelId.ts` and `apps/webapp/app/routes/admin.api.v1.llm-models.ts`, the `startDate` field in `UpdateModelSchema` and `CreateModelSchema` intentionally uses `z.string().optional()` (or `.nullable().optional()`) without strict ISO datetime validation. Invalid date strings are rejected at the Prisma/DB layer. This is acceptable because these are admin-only API routes protected by Personal Access Token (PAT) authentication and are not user-facing.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2025-07-12T18:00:06.163Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2264
File: apps/webapp/app/utils/searchParams.ts:16-18
Timestamp: 2025-07-12T18:00:06.163Z
Learning: The `objectToSearchParams` function in `apps/webapp/app/utils/searchParams.ts` is used to generate URL parameters from objects and is separate from code that parses incoming search parameters. Changes to this function only affect places where it's used to create URLs, not places that parse search parameters from external sources.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-01-28T14:15:15.011Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 2953
File: apps/webapp/app/components/runs/v3/SharedFilters.tsx:441-452
Timestamp: 2026-01-28T14:15:15.011Z
Learning: In apps/webapp/app/components/runs/v3/SharedFilters.tsx, the maxPeriodDays limit for date ranges should only check the from date (via dateRangeToDays(fromValue)) because it enforces data retention limits—how far back in history queries can reach. The to date is irrelevant for retention-based limits.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx
📚 Learning: 2026-02-11T16:37:32.429Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3019
File: apps/webapp/app/components/primitives/charts/Card.tsx:26-30
Timestamp: 2026-02-11T16:37:32.429Z
Learning: In projects using react-grid-layout, avoid relying on drag-handle class to imply draggability. Ensure drag-handle elements only affect dragging when the parent grid item is configured draggable in the layout; conditionally apply cursor styles based on the draggable prop. This improves correctness and accessibility.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsx

Copy link
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 found 5 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

Comment on lines +51 to +52
firstSeen: new Date(data.error.firstSeen),
lastSeen: new Date(data.error.lastSeen),
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Webhook payload produces Invalid Date for firstSeen/lastSeen fields

In errorGroupWebhook.server.ts:51-52, new Date(data.error.firstSeen) and new Date(data.error.lastSeen) are called with string arguments that are millisecond-epoch timestamps (e.g., "1712345678901"), because the upstream ClickHouse query (internal-packages/clickhouse/src/errors.ts:391-392) formats them via toString(toUnixTimestamp64Milli(...)). JavaScript's Date constructor treats string arguments as date-parseable strings (ISO 8601), not numeric timestamps — confirmed that new Date("1712345678901") returns Invalid Date in Node.js. This means all webhook payloads will contain invalid dates, causing schema validation failures or delivering broken data to consumers.

Suggested change
firstSeen: new Date(data.error.firstSeen),
lastSeen: new Date(data.error.lastSeen),
firstSeen: new Date(Number(data.error.firstSeen)),
lastSeen: new Date(Number(data.error.lastSeen)),
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +295 to +299
if (statuses && statuses.length > 0) {
transformedErrorGroups = transformedErrorGroups.filter((g) =>
statuses.includes(g.status as ErrorGroupStatus)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Status filter applied after pagination produces incorrect page sizes and broken cursors

In ErrorsListPresenter.server.ts, the statuses filter (lines 295-298) is applied as a post-filter on already-paginated results from ClickHouse. The ClickHouse query fetches pageSize + 1 rows (line 242), pagination cursors and hasMore are computed from the unfiltered result set (lines 251-266), and only then is the status filter applied. This causes two problems: (1) pages can return significantly fewer than pageSize items even when more matching items exist in the database, and (2) pagination cursors point to positions in the unfiltered stream, so "next page" may show results that were already skipped, or show empty pages. The status filter should be pushed into the database query or applied before pagination logic.

Prompt for agents
In apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts, the status filter at lines 295-299 is applied after ClickHouse pagination (pageSize+1 fetch at line 242, cursor computation at lines 258-266). This breaks pagination when filtering by status. The fix requires restructuring: either (1) join with ErrorGroupState in the presenter and filter before computing pagination, fetching additional pages from ClickHouse as needed, or (2) move the status filter before the pagination cursor/hasMore computation, re-fetching from ClickHouse if the filtered set is too small. The key constraint is that status lives in Postgres (ErrorGroupState table) while the error list comes from ClickHouse, so they cannot be combined in a single query.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +413 to +428
const states = await this.replica.errorGroupState.findMany({
where: {
environmentId,
OR: errorGroups.map((e) => ({
taskIdentifier: e.task_identifier,
errorFingerprint: e.error_fingerprint,
})),
},
select: {
taskIdentifier: true,
errorFingerprint: true,
status: true,
resolvedAt: true,
ignoredUntil: true,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 Prisma OR query in getErrorGroupStates may return unexpected cross-matches

In ErrorsListPresenter.server.ts:416-419, the Prisma query uses OR with an array of {taskIdentifier, errorFingerprint} pairs. Prisma translates OR conditions as independent predicates, meaning a row matching taskIdentifier from one pair and errorFingerprint from a different pair would still be returned. This could produce incorrect state lookups if different tasks share fingerprints. The map key at line 431 (${state.taskIdentifier}:${state.errorFingerprint}) would correctly index the spurious results, but they'd simply be unused entries in the map rather than causing incorrect behavior. This is safe but slightly wasteful for large datasets with shared fingerprints.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +181 to +189
interval: 60_000,
onLoad: false,
callback: useCallback(() => {
if (revalidator.state === "idle") {
revalidator.revalidate();
}
}, [revalidator]),
});

Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 60-second auto-revalidation added to errors list page

The errors list page now auto-revalidates every 60 seconds via useInterval at _app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx:181-189. This will cause the parent layout route's loader (errors/route.tsx) to also re-run on every revalidation, fetching alert channel data and Slack status every 60 seconds for every user viewing the errors page. This may increase load on the Slack API and database, especially with many concurrent users. Worth monitoring.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants