Conversation
matt-aitken
commented
Mar 5, 2026
- Added versions filtering on the Errors list and page
- Added errors stacked bars to the graph on the individual error page
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds 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)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment Tip Migrating from UI to YAML configuration.Use the |
...tes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
Show resolved
Hide resolved
cd9b5ea to
e609e8b
Compare
There was a problem hiding this comment.
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 | 🔴 CriticalMissing
ERROR_GROUPcase inalertTypeTitlewill cause runtime error.The
ProjectAlertTypeenum now includesERROR_GROUP(added in this PR's schema changes), butalertTypeTitledoesn't handle it. When an alert channel withERROR_GROUPtype 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()declaresErrorGroupSummaryas return type but returns an incomplete object withoutstate.The
getSummary()method at line 249 declares a return type ofPromise<ErrorGroupSummary | undefined>, whereErrorGroupSummary.stateis required. However, the method returns an object (lines 276–285) that omits thestatefield entirely. While thecall()method does assignstateafter retrieval (line 149), this violates the type contract ofgetSummary(). Either makestateoptional in the type definition, includestateingetSummary()'s return object, or updategetSummary()'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@crumbstracing annotations for the new error-query paths.Please add crumb comments around the newly added
errorsquery builder exposure to align with repository tracing standards.As per coding guidelines,
**/*.{ts,tsx,js}: “Add crumbs as you write code using //@crumbscomments or //#region@crumbsblocks 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 onSubmitOr 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
typeinstead ofinterfaceto 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.
AlertableErrorandResolvedEnvironmentshould usetypealiases 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
📒 Files selected for processing (21)
apps/webapp/app/components/logs/LogsVersionFilter.tsxapps/webapp/app/components/primitives/charts/ChartRoot.tsxapps/webapp/app/components/runs/v3/RunFilters.tsxapps/webapp/app/models/projectAlert.server.tsapps/webapp/app/presenters/v3/ErrorGroupPresenter.server.tsapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/v3/alertsWorker.server.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.tsapps/webapp/app/v3/services/errorGroupActions.server.tsinternal-packages/clickhouse/src/errors.tsinternal-packages/clickhouse/src/index.tsinternal-packages/database/prisma/migrations/20260306102053_error_group_state/migration.sqlinternal-packages/database/prisma/migrations/20260308181657_add_error_alert_config_to_project_alert_channel/migration.sqlinternal-packages/database/prisma/schema.prismainternal-packages/emails/emails/alert-error-group.tsxinternal-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
apps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.ts
Outdated
Show resolved
Hide resolved
6875ac8 to
497f301
Compare
There was a problem hiding this comment.
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 | 🟠 MajorApply
versionsto the activity query as well.This only filters the list query. The
occurrencesPromisebelow still callspresenter.getOccurrences()withoutversions, 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 | 🟠 MajorFilter 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
pageSizematching 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 requiredstatefield.The return type
ErrorGroupSummary(line 85) requiresstate: ErrorGroupState, but the object literal returned at lines 276–285 does not include it. This causes a TypeScript compilation error. Whilecall()decorates the summary withstateafterwards,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 | 🟡 MinorJob 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 enabledERROR_GROUPchannels 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 | 🟠 MajorAvoid 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 | 🟠 MajorThe page is still fingerprint-scoped, but the new actions are task-scoped.
ErrorGroupActionsidentifies state bytaskIdentifier + errorFingerprint, yet this loader still fetches summary, activity, and runs byfingerprintalone. If the same fingerprint exists under multiple tasks, the page can render aggregate data for all of them and then resolve/ignore only whichevertaskIdentifierhappened to come back in the summary. ThreadtaskIdentifierthrough 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
📒 Files selected for processing (27)
apps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/components/logs/LogsVersionFilter.tsxapps/webapp/app/components/primitives/charts/ChartRoot.tsxapps/webapp/app/components/runs/v3/RunFilters.tsxapps/webapp/app/models/projectAlert.server.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/presenters/v3/ErrorGroupPresenter.server.tsapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/utils/pathBuilder.tsapps/webapp/app/v3/alertsWorker.server.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/v3/services/alerts/errorAlertEvaluator.server.tsapps/webapp/app/v3/services/errorGroupActions.server.tsinternal-packages/clickhouse/src/errors.tsinternal-packages/clickhouse/src/index.tsinternal-packages/database/prisma/migrations/20260306102053_error_group_state/migration.sqlinternal-packages/database/prisma/migrations/20260308181657_add_error_alert_config_to_project_alert_channel/migration.sqlinternal-packages/database/prisma/migrations/20260320115950_add_ignored_at_occurrence_count_to_error_group_state/migration.sqlinternal-packages/database/prisma/schema.prismainternal-packages/emails/emails/alert-error-group.tsxinternal-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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxinternal-packages/clickhouse/src/errors.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxinternal-packages/clickhouse/src/errors.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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
envexport ofenv.server.tsinstead of directly accessingprocess.envin the Trigger.dev webapp
Files:
apps/webapp/app/utils/pathBuilder.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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/corein 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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsinternal-packages/clickhouse/src/errors.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxinternal-packages/clickhouse/src/errors.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js}: Always import from@trigger.dev/sdkfor Trigger.dev tasks - never use@trigger.dev/sdk/v3or deprecated client.defineJob
Import subpaths only from@trigger.dev/core, never import from root
Add crumbs as you write code using //@crumbscomments or //#region@crumbsblocks for agentcrumbs debug tracing
Files:
apps/webapp/app/utils/pathBuilder.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxinternal-packages/clickhouse/src/errors.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.connect-to-slack.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts
apps/webapp/**/*.server.ts
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Access environment variables via the
envexport fromapp/env.server.ts, never useprocess.envdirectly
Files:
apps/webapp/app/v3/services/alerts/createAlertChannel.server.tsapps/webapp/app/presenters/v3/ErrorAlertChannelPresenter.server.tsapps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.tsapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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
RunEngineVersionto 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.tsapps/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.tsapps/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.tsfor/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.tsapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/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.tsapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/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.tsapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/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.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors.$fingerprint/route.tsxapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsxapps/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.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/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.tsxapps/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.tsxapps/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.prismaapps/webapp/app/presenters/v3/ErrorsListPresenter.server.tsapps/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.tsapps/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.tsapps/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
| 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(), | ||
| }), | ||
| ]); |
There was a problem hiding this comment.
🧩 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 -5Repository: 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 -10Repository: 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 -50Repository: 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 -40Repository: triggerdotdev/trigger.dev
Length of output: 167
🏁 Script executed:
# Search for ErrorGroupState and taskIdentifier usage
rg "ErrorGroupState|errorGroup\.taskIdentifier" apps/webapp -A 3 -B 1Repository: 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 -20Repository: 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
fiRepository: 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 -30Repository: 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 20Repository: 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 3Repository: 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 -80Repository: 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 -60Repository: 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 -40Repository: triggerdotdev/trigger.dev
Length of output: 96
🏁 Script executed:
# Find the file with ErrorGroupActions
fd "ErrorGroupActions" apps/webapp --type ts --type tsxRepository: 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.prismaRepository: triggerdotdev/trigger.dev
Length of output: 51
🏁 Script executed:
# Find ErrorGroupActions
rg "ErrorGroupActions" apps/webapp -lRepository: 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 5Repository: triggerdotdev/trigger.dev
Length of output: 832
🏁 Script executed:
# Read the ErrorGroupActions service
wc -l apps/webapp/app/v3/services/errorGroupActions.server.tsRepository: triggerdotdev/trigger.dev
Length of output: 128
🏁 Script executed:
# Read key methods from ErrorGroupActions
cat apps/webapp/app/v3/services/errorGroupActions.server.ts | head -300Repository: 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.
| 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.
...app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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 | 🟡 MinorPreserve
alertswhen clearing filters.These empty GET forms wipe the whole query string, so clicking “Clear all filters” also removes
alerts=trueand closes the side panel. Keep non-filter params likealertsin 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` `@crumbsblock) 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 //
@crumbscomments or //#region@crumbsblocks 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
📒 Files selected for processing (7)
apps/webapp/app/components/errors/ConfigureErrorAlerts.tsxapps/webapp/app/components/layout/AppLayout.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/storybook.unordered-list/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
apps/webapp/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Access all environment variables through the
envexport ofenv.server.tsinstead of directly accessingprocess.envin the Trigger.dev webapp
Files:
apps/webapp/app/components/layout/AppLayout.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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/corein 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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/webapp/app/components/errors/ConfigureErrorAlerts.tsx
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js}: Always import from@trigger.dev/sdkfor Trigger.dev tasks - never use@trigger.dev/sdk/v3or deprecated client.defineJob
Import subpaths only from@trigger.dev/core, never import from root
Add crumbs as you write code using //@crumbscomments or //#region@crumbsblocks for agentcrumbs debug tracing
Files:
apps/webapp/app/components/layout/AppLayout.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/webapp/app/components/primitives/UnorderedList.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors/route.tsxapps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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.tsxapps/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
classNameprop follows the established pattern used by other layout components in this file (AppContainer,PageBody, etc.) and correctly uses thecnutility 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
...tes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx
Show resolved
Hide resolved
| 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 } }); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | 🔴 CriticalInitialize
stateingetSummary()now that it is required.
ErrorGroupSummarynow requiresstate, butgetSummary()still returns an object without that field. This breaks the file’s TypeScript contract beforecall()gets a chance to overwritesummary.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 | 🟠 MajorState lookup still needs
taskIdentifier.
ErrorGroupStateis keyed by(environmentId, taskIdentifier, errorFingerprint), but this code still fetches it with only(environmentId, errorFingerprint)and does so beforesummary.taskIdentifieris known. If the same fingerprint exists under multiple tasks, the page can surface the wrong resolved/ignored state. Fetchsummaryfirst, then look up state via the full composite key (findUniquefits 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 | 🟠 MajorReject empty task identifiers and non-positive ignore thresholds server-side.
These branches still allow an empty
taskIdentifierand zero/negative ignore thresholds through toErrorGroupActions. That lets crafted POSTs createErrorGroupStaterows 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.1are 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
📒 Files selected for processing (2)
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.tsapps/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.tsapps/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.tsapps/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.tsapps/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
envexport ofenv.server.tsinstead of directly accessingprocess.envin the Trigger.dev webapp
Files:
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.tsapps/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/corein 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.tsapps/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.tsapps/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
envexport fromapp/env.server.ts, never useprocess.envdirectly
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.tsapps/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/sdkfor Trigger.dev tasks - never use@trigger.dev/sdk/v3or deprecated client.defineJob
Import subpaths only from@trigger.dev/core, never import from root
Add crumbs as you write code using //@crumbscomments or //#region@crumbsblocks for agentcrumbs debug tracing
Files:
apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.tsapps/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.tsapps/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.tsapps/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
...app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.alerts/route.tsx
Show resolved
Hide resolved
| firstSeen: new Date(data.error.firstSeen), | ||
| lastSeen: new Date(data.error.lastSeen), |
There was a problem hiding this comment.
🔴 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.
| 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)), |
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (statuses && statuses.length > 0) { | ||
| transformedErrorGroups = transformedErrorGroups.filter((g) => | ||
| statuses.includes(g.status as ErrorGroupStatus) | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔴 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🚩 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| interval: 60_000, | ||
| onLoad: false, | ||
| callback: useCallback(() => { | ||
| if (revalidator.state === "idle") { | ||
| revalidator.revalidate(); | ||
| } | ||
| }, [revalidator]), | ||
| }); | ||
|
|
There was a problem hiding this comment.
🚩 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.
Was this helpful? React with 👍 or 👎 to provide feedback.