fix(webapp): stop session durations climbing forever when no run is live - #4570
fix(webapp): stop session durations climbing forever when no run is live#4570D-K-P wants to merge 7 commits into
Conversation
Session status was derived only from closedAt/expiresAt, so an open session whose run had already finished stayed Active forever and its duration ticked up from createdAt without end. Status is now derived from the current run's liveness: a session with no live run reads Idle, and its duration freezes at the run's completion instead of counting up. Active is reserved for sessions with a run actually executing. Applies to the sessions list and the session detail page.
The tag filter matches the session's own top-level tags, not triggerConfig.tags.
|
|
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:
WalkthroughSession presenters now determine whether the current run is active and expose its completion timestamp. Session duration rendering uses live updates only for active runs, freezes completed or terminal sessions, and shows a dash for sessions without completed runs. Tests cover live-state logic and presenter behavior. Vitest now includes all presenter tests. Session tag documentation and the changelog were updated. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 |
Address review on the sessions status change: - Keep the "Close session" action on the detail page for Idle sessions; they are open, only Closed and Expired are terminal. - Rename the status helper input from currentRunId to hasCurrentRun, since the detail page passes a run friendlyId, not the session's currentRunId. - Restore the Active tooltip copy so it stays accurate now that the Active filter also returns open, idle sessions. - Align the sessions docs example so the listed tag matches a top-level tag set at start time.
…ing-duration # Conflicts: # apps/webapp/vitest.config.ts
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
@trigger.dev/build
trigger.dev
@trigger.dev/core
@trigger.dev/python
@trigger.dev/react-hooks
@trigger.dev/redis-worker
@trigger.dev/rsc
@trigger.dev/schema-to-json
@trigger.dev/sdk
commit: |
…ing-duration # Conflicts: # apps/webapp/vitest.config.ts
The run/span panel derived the session badge from closedAt/expiresAt only, so it could read Active where the sessions list and detail page now read Idle. It now uses the same run-liveness derivation as the rest of the sessions surface.
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Sessions kept an ever-growing wall-clock duration because the cell ticked for any open session, even when its run had finished long ago. The duration now ticks only while a run is genuinely executing; otherwise it freezes at the last run's completion (or shows a dash if it never ran). Session status stays the existing filterable ACTIVE/CLOSED/EXPIRED set, so there is nothing new to filter. This drops the earlier display-only IDLE status, which was not filterable.
e431608 to
3dfca81
Compare
| // Whether a run is genuinely executing right now. Drives the duration | ||
| // cell (tick vs freeze); it does NOT affect the filterable status. | ||
| const hasLiveRun = isSessionLive({ | ||
| hasCurrentRun: session.currentRunId != null, | ||
| currentRunStatus: currentRun?.status, | ||
| }); |
There was a problem hiding this comment.
🔍 PR description promises an "Idle" status but the code only changes the duration cell
The PR title and description say sessions with no live run should read "Idle" and that "the same derivation now backs the session detail page". The diff does neither: status in apps/webapp/app/presenters/v3/SessionListPresenter.server.ts:212-217 still only derives ACTIVE/CLOSED/EXPIRED from closedAt/expiresAt, no IDLE value is added to SessionStatus or SessionStatusCombo, and SessionPresenter.server.ts (detail page) is untouched. Only the duration cell freezes. Worth confirming whether the status half of the change was intentionally dropped, since the release note in .server-changes/sessions-idle-status.md (file name mentions "idle status") may also mislead users.
Was this helpful? React with 👍 or 👎 to provide feedback.
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/SessionListPresenter.server.ts (1)
200-200: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUpdate the current-run query comment.
The comment above Line 200 says that status is not fetched. Line 200 now selects
statusandcompletedAt. Update the comment to describe the current query.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d230a07c-f44a-4ef2-9395-684a35355d2b
📒 Files selected for processing (6)
.server-changes/sessions-idle-status.mdapps/webapp/app/components/sessions/v1/SessionsTable.tsxapps/webapp/app/presenters/v3/SessionListPresenter.server.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/test/sessionListPresenterStatus.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- .server-changes/sessions-idle-status.md
- apps/webapp/app/components/sessions/v1/SessionsTable.tsx
- apps/webapp/test/sessionListPresenterStatus.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (13)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (8, 12)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (2, 12)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (12, 12)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (7, 12)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (11, 12)
- GitHub Check: webapp / 🧪 Unit Tests: Webapp (6, 12)
- GitHub Check: runops-guard / runops-guard
- GitHub Check: obsmap / 🧪 Unit Tests: Observability Map
- GitHub Check: e2e-webapp / 🧪 E2E Tests: Webapp
- GitHub Check: code-quality / code-quality
- GitHub Check: audit
- GitHub Check: audit
- GitHub Check: Analyze (javascript-typescript)
🧰 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}: Prefer static imports over dynamicimport(); use dynamic imports only for unresolvable circular dependencies, genuine performance code splitting, or conditional runtime loading.
Import Trigger.dev tasks from@trigger.dev/sdk; never use@trigger.dev/sdk/v3or deprecatedclient.defineJob.
Add agentcrumbs while writing code using approved namespaces; mark lines with//@Crumbsor blocks with `// `#region` `@crumbs, and strip them before merging.
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.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/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.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/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
apps/webapp/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
apps/webapp/**/*.{ts,tsx}: Access environment variables through theenvexport ofenv.server.tsinstead of directly accessingprocess.env
Use subpath exports from@trigger.dev/corepackage instead of importing from the root@trigger.dev/corepathDo not reintroduce the removed v1 execution path;
RunEngineVersion.V1branches may only reject or finalize gracefully so v3 clients receive a clean 4xx, never a 5xx.
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
apps/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
For apps, use
typecheckfor verification and never usebuildas the correctness check.
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
apps/webapp/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
apps/webapp/app/**/*.{ts,tsx}: For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.
UseuseCallbackanduseMemoonly for context provider values, expensive derived data used as a dependency, or stable references required by dependency arrays; do not wrap ordinary event handlers or trivial computations.
Use named constants for sentinel or placeholder values instead of scattering raw string literals across comparisons.
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
apps/webapp/app/**/*.ts
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
apps/webapp/app/**/*.ts: Never userequest.signalto detect client disconnects. UsegetRequestAbortSignal()fromapp/services/httpAsyncStorage.server.ts, which is wired to Express response close events.
Access environment variables through theenvexport fromapp/env.server.ts; never useprocess.envdirectly.
Always use PrismafindFirstinstead offindUnique.
Always use the$transactionhelper from~/db.server, never callprisma.$transactionor$replica.$transactiondirectly. Pass isolation levels as strings, useSerializablefor correctness-critical read-then-write invariants, and guard possibly undefined helper results when a definite value is required.
Files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use vitest for all tests in the Trigger.dev repository
**/*.{test,spec}.{ts,tsx}: Use Vitest exclusively and never mock dependencies; use Testcontainers for integration dependencies.
Place test files next to the source files they test.
Files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
apps/webapp/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Do not import
env.server.tsdirectly or indirectly into test files; instead pass environment-dependent values through options/parameters to make code testable
Files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
apps/webapp/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Test files must not import
app/env.server.ts; pass configuration as options instead.
Files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
🧠 Learnings (45)
📓 Common learnings
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 4284
File: apps/webapp/app/services/realtime/sessionRunManager.server.ts:481-484
Timestamp: 2026-07-18T13:08:46.931Z
Learning: In `apps/webapp/app/services/realtime/sessionRunManager.server.ts`, `getRunStatusAndFriendlyId` intentionally reads from `$replica` on the steady-state append hot path. S2 input streams use `canonicalSessionAddressingKey` keyed by a session externalId/friendlyId rather than a run ID, so appends remain durable even when a stale replica reports a terminal run as non-final. After replica catch-up, a subsequent `ensureRunForSession` detects the terminal run, triggers a continuation, and its boot gate replays the durable `session.in` tail. Do not require a primary re-check for every non-final replica result solely to prevent data loss; the tradeoff is accepted. A trailing fire-and-forget append can remain pending until a later call, which is a known latency consideration.
📚 Learning: 2026-07-18T13:08:46.931Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 4284
File: apps/webapp/app/services/realtime/sessionRunManager.server.ts:481-484
Timestamp: 2026-07-18T13:08:46.931Z
Learning: In `apps/webapp/app/services/realtime/sessionRunManager.server.ts`, `getRunStatusAndFriendlyId` intentionally reads from `$replica` on the steady-state append hot path. S2 input streams use `canonicalSessionAddressingKey` keyed by a session externalId/friendlyId rather than a run ID, so appends remain durable even when a stale replica reports a terminal run as non-final. After replica catch-up, a subsequent `ensureRunForSession` detects the terminal run, triggers a continuation, and its boot gate replays the durable `session.in` tail. Do not require a primary re-check for every non-final replica result solely to prevent data loss; the tradeoff is accepted. A trailing fire-and-forget append can remain pending until a later call, which is a known latency consideration.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-04-20T15:09:12.730Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3417
File: internal-packages/clickhouse/src/sessions.ts:174-180
Timestamp: 2026-04-20T15:09:12.730Z
Learning: In `internal-packages/clickhouse/src/sessions.ts`, `getSessionTagsQueryBuilder` intentionally queries `trigger_dev.sessions_v1` WITHOUT `FINAL`, mirroring `getTaskRunTagsQueryBuilder` which queries `task_runs_v2` without `FINAL`. The DISTINCT arrayJoin tag-listing read can tolerate an occasional stale tag from a superseded ReplacingMergeTree row; the FINAL cost on a large table is considered not worth it. If FINAL is ever added, both tag query builders (sessions and runs) will be updated together. Do not flag the missing FINAL in either tag query builder as a consistency or stale-data issue.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-08T09:27:50.797Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3529
File: packages/cli-v3/src/executions/taskRunProcess.ts:216-220
Timestamp: 2026-05-08T09:27:50.797Z
Learning: In triggerdotdev/trigger.dev (`packages/cli-v3/src/executions/taskRunProcess.ts`), stale `_currentExecution` / `_isPreparedForNextAttempt` after an error-path rejection (e.g. `#rejectPendingAttempts`) is benign: `#gracefullyTerminate` immediately calls `kill()`, which synchronously sets `_isBeingKilled = true`, and the `isHealthy` getter returns `false` whenever `isBeingKilled` is true — preventing any caller from reusing the process. Both known callers (`dev-run-controller.ts` ~lines 516-567 and `execution.ts` ~lines 538-591) also handle the error and discard the process instance. Do not flag missing cleanup of these fields on error paths in this class.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.ts
📚 Learning: 2026-07-18T18:17:26.381Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 4285
File: internal-packages/run-store/src/runOpsStore.sessionRunProbeReadAfterWrite.test.ts:154-182
Timestamp: 2026-07-18T18:17:26.381Z
Learning: For session-run replica-lag coverage, `internal-packages/run-store/src/runOpsStore.sessionRunProbeReadAfterWrite.test.ts` intentionally characterizes only the `RoutingRunStore.findRun` store seam: an owning-replica miss followed by a writer read recovers the live run. The behavioral no-double-trigger contract belongs in `apps/webapp/test/realtimeServices.replicaLag.test.ts`, which invokes the real exported `ensureRunForSession` and asserts reuse (`triggered: false`) with zero `TriggerTaskService` calls.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-04T15:28:32.311Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3834
File: apps/webapp/app/components/runs/v3/agent/AgentView.tsx:300-340
Timestamp: 2026-06-04T15:28:32.311Z
Learning: In `apps/webapp/app/components/runs/v3/agent/AgentView.tsx` (triggerdotdev/trigger.dev), `applyToolResolution()` inside `useAgentSessionMessages` treats `output-denied` as a terminal tool state alongside `output-available` and `output-error`. A buffered `.in` resolution replay must never overwrite a part that has already reached any of these three terminal states. This mirrors the same contract enforced by `mergeIncomingIntoHydrated` in `packages/trigger-sdk/src/v3/ai.ts`.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/SessionListPresenter.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/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-03-22T13:26:12.060Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3244
File: apps/webapp/app/components/code/TextEditor.tsx:81-86
Timestamp: 2026-03-22T13:26:12.060Z
Learning: In the triggerdotdev/trigger.dev codebase, do not flag `navigator.clipboard.writeText(...)` calls for `missing-await`/`unhandled-promise` issues. These clipboard writes are intentionally invoked without `await` and without `catch` handlers across the project; keep that behavior consistent when reviewing TypeScript/TSX files (e.g., usages like in `apps/webapp/app/components/code/TextEditor.tsx`).
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-03-22T19:24:14.403Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/v3/services/alerts/deliverErrorGroupAlert.server.ts:200-204
Timestamp: 2026-03-22T19:24:14.403Z
Learning: In the triggerdotdev/trigger.dev codebase, webhook URLs are not expected to contain embedded credentials/secrets (e.g., fields like `ProjectAlertWebhookProperties` should only hold credential-free webhook endpoints). During code review, if you see logging or inclusion of raw webhook URLs in error messages, do not automatically treat it as a credential-leak/secrets-in-logs issue by default—first verify the URL does not contain embedded credentials (for example, no username/password in the URL, no obvious secret/token query params or fragments). If the URL is credential-free per this project’s conventions, allow the logging.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma error P1001 ("Can't reach database server") in TypeScript, don’t assume a single error shape. Prisma can surface P1001 via two different error classes/fields: `PrismaClientKnownRequestError` exposes it as `err.code === "P1001"` (common during mid-query connection drops), while `PrismaClientInitializationError` exposes it as `err.errorCode === "P1001"` (common on client startup failure). Therefore, predicates should use `err.code === "P1001" || err.errorCode === "P1001"`. Do not flag `err.code === "P1001"` as “unreachable/never matches,” as it is expected in production.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-18T08:21:27.694Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3632
File: apps/webapp/sentry.server.ts:4-21
Timestamp: 2026-05-18T08:21:27.694Z
Learning: When handling Prisma errors for P1001 ("Can't reach database server"), do not assume it only appears under a single property name. Prisma may surface P1001 via either `PrismaClientKnownRequestError` (`err.code === "P1001"`, e.g., mid-query connection drops) or `PrismaClientInitializationError` (`err.errorCode === "P1001"`, e.g., client startup connection failure). To reliably detect the condition, check `err.code === "P1001" || err.errorCode === "P1001"`, and avoid review rules that would incorrectly flag `err.code === "P1001"` as unreachable/never-matching.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-13T19:53:13.759Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3937
File: packages/trigger-sdk/skills/realtime-and-frontend/SKILL.md:258-260
Timestamp: 2026-06-13T19:53:13.759Z
Learning: When reviewing code that uses `trigger.dev/react-hooks`’s `useRealtimeRun`, preserve the call signature where the first argument is the full realtime handle object (not `handle.id`). This is intentional to maintain type-safety and is consistent with the official docs; do not suggest changing the first argument from the handle object to `handle.id`.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-17T17:13:49.929Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3948
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx:48-62
Timestamp: 2026-06-17T17:13:49.929Z
Learning: In triggerdotdev/trigger.dev, within `dashboardLoader`/`dashboardAction` (or similar context resolver code) whenever you resolve an organization ID from an organization slug for RBAC/enterprise authorization scope, always read from the primary Prisma client (`prisma`), not `$replica`. Using `$replica` can hit replica-lag and cause the RBAC lookup/authorization to run without the correct org scope (bypassing intended role enforcement). Implement the slug→org lookup with `prisma.organization.findFirst(...)` (or equivalent primary-client query) and add an inline comment documenting why the primary client is required (replica lag could lead to unscoped RBAC checks).
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-23T13:04:21.413Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4023
File: apps/webapp/app/services/upsertBranch.server.ts:14-18
Timestamp: 2026-06-23T13:04:21.413Z
Learning: In TypeScript, it’s valid to `import { type X }` and then use `typeof X` in a type-only position, e.g. `type Alias = z.infer<typeof X>`. The `type` modifier suppresses the runtime import, but the type checker still has the full exported type so `z.infer<typeof X>` can resolve correctly. In code reviews, don’t flag this as a TypeScript compile error as long as `typeof X` is used in a type context (e.g., with `z.infer`, `type` aliases, generics), not as a runtime value.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-12T21:04:05.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3542
File: apps/webapp/app/components/sessions/v1/SessionStatus.tsx:1-3
Timestamp: 2026-05-12T21:04:05.815Z
Learning: In this Remix + TypeScript codebase, do not flag a server/client boundary violation when a file imports only types from a module matching `*.server`.
Specifically, it’s safe to import types using `import type { Foo } from "*.server"` or `import { type Foo } from "*.server"` because TypeScript erases type-only imports at compile time and they emit no JavaScript, so they won’t cross the Remix server/client bundle boundary.
Only raise the boundary concern for value imports (e.g., `import { Foo }` without `type`, or `import Foo`), since those produce JavaScript output.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-25T18:21:51.905Z
Learnt from: carderne
Repo: triggerdotdev/trigger.dev PR: 4039
File: apps/webapp/app/routes/invite-revoke.tsx:0-0
Timestamp: 2026-06-25T18:21:51.905Z
Learning: During the Zod v4 migration in the triggerdotdev/trigger.dev webapp, ensure any imports from `conform-to/zod` use the Zod-4 subpath: `conform-to/zod/v4` (e.g., `import { parseWithZod } from "conform-to/zod/v4"`). Do not import from the package root `conform-to/zod`, because it is the Zod 3 implementation and may load Zod-3-only symbols (e.g., `ZodBranded`, `ZodEffects`), which can throw at module load (notably with `zod4.4.3`). This should be enforced across `apps/webapp/**/*` where helpers like `parseWithZod` and `conformZodMessage` are used.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-07-03T17:10:21.498Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 4148
File: apps/webapp/app/models/orgMember.server.ts:149-168
Timestamp: 2026-07-03T17:10:21.498Z
Learning: In triggerdotdev/trigger.dev, `User.email` (Prisma schema: `internal-packages/database/prisma/schema.prisma`) currently does NOT use `citext` and does NOT have a `lower(email)` functional unique index. Therefore, do not introduce Prisma queries like `where: { email: { equals: <value>, mode: "insensitive" } }` (or any case-insensitive lookup) against `User.email`, because it can force sequential scans of the `users` table under load. During review, ensure email is normalized (e.g., lowercased/trimmed) before both writes and subsequent lookups, and if true case-insensitive behavior/uniqueness is required, implement it via a separate app-wide migration (e.g., switch to `citext` and/or add a functional unique index with backfill) rather than bolting it onto individual feature PRs.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-04T18:16:35.386Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 3836
File: apps/supervisor/src/backpressure/backpressureMonitor.ts:3-5
Timestamp: 2026-06-04T18:16:35.386Z
Learning: When reviewing TypeScript in this repo, apply the rule “prefer type aliases over interfaces” only to data/object shapes and union/intersection type modeling. If an interface is being used as a behavioral contract for collaborators to implement (e.g., method-shape interfaces that define required behavior, such as `BackpressureLogger` / `BackpressureSignalSource` in `apps/supervisor/src/backpressure/backpressureMonitor.ts`), keep it as an `interface` and do not flag it as a type-alias-vs-interface violation.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-09T17:58:04.699Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 3879
File: apps/webapp/app/models/vercelIntegration.server.ts:619-630
Timestamp: 2026-06-09T17:58:04.699Z
Learning: In this codebase, outbound raw `fetch` calls should typically rely on Node/undici’s default request timeout (about ~300s) rather than adding a per-call `AbortController` + `setTimeout` wrapper inside individual functions (e.g. in files like `apps/webapp/app/models/vercelIntegration.server.ts`). During code review, do not flag the absence of a per-call timeout on a single `fetch` as an issue; if per-call timeouts are needed, they should be implemented via a codebase-wide convention (e.g., a shared fetch wrapper or documented pattern) rather than ad-hoc per-function changes.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.tsapps/webapp/app/presenters/v3/isSessionLive.test.tsapps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-08-12T08:49:25.047Z
Learnt from: kathiekiwi
Repo: triggerdotdev/trigger.dev PR: 4516
File: apps/webapp/test/queryRouteReadOnly.test.ts:38-84
Timestamp: 2026-08-12T08:49:25.047Z
Learning: In `apps/webapp/test/queryRouteReadOnly.test.ts`, Vitest module mocks are intentional for route tests that must prove rejected write queries do not call ClickHouse, persistence, or concurrency infrastructure. Use container-backed tests when persistence behavior is under test, not to replace this verify-not-called seam.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-07-27T15:07:14.579Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 4397
File: apps/webapp/test/batchStreamGrants.test.ts:0-0
Timestamp: 2026-07-27T15:07:14.579Z
Learning: In `triggerdotdev/trigger.dev`, `apps/webapp/test/authorizationRateLimitMiddleware.test.ts` remains skipped because correcting its plaintext Redis fixture setup (`tlsDisabled`) exposed a timing-sensitive sliding-window test based on real 10-second windows. The new authorization-rate-limit bypass coverage instead lives in the unskipped, deterministic `apps/webapp/test/authorizationRateLimitMiddlewareBypass.test.ts`. Do not flag the legacy suite's continued skip as missing coverage for the bypass behavior.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-08-11T22:35:59.403Z
Learnt from: kathiekiwi
Repo: triggerdotdev/trigger.dev PR: 4556
File: apps/webapp/test/dashboardAgentWatchLimitStatus.test.ts:22-55
Timestamp: 2026-08-11T22:35:59.403Z
Learning: In `apps/webapp/test/dashboardAgentWatchLimitStatus.test.ts`, route-level tests may use narrow Vitest module seams to provide authentication, access control, and deterministic plan-limit configuration when the test uses `postgresTest` with real Prisma seeding and a real dashboard-agent database client. Do not flag these seams as Testcontainers violations when the test verifies a route response and container-backed persistence behavior remains real.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-06-16T09:19:47.637Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3960
File: apps/webapp/test/prismaInfrastructureErrorCapture.test.ts:0-0
Timestamp: 2026-06-16T09:19:47.637Z
Learning: In this repo’s Vitest setup, `vitest.config.ts` uses `globals: true`, so identifiers like `vi`, `describe`, `it`, and `expect` are available as globals in Vitest test files. During code review, do not flag missing `vi`/`describe`/`it`/`expect` imports as a runtime error or correctness issue when they’re used in `*.test.ts/tsx` or `*.spec.ts/tsx` files. Explicit imports are still preferred for consistency, but they’re not required for runtime behavior.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.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 **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-08-11T21:56:48.387Z
Learnt from: kathiekiwi
Repo: triggerdotdev/trigger.dev PR: 4529
File: apps/webapp/test/contextlessPatRoutes.test.ts:32-38
Timestamp: 2026-08-11T21:56:48.387Z
Learning: In `apps/webapp/test/contextlessPatRoutes.test.ts`, route-builder authorization unit tests may use Vitest mocks when they must assert that denied requests do not call persistence or infrastructure dependencies. Container-backed tests should cover flows where persistence behavior is under test, such as `userActorPatOnlyBoundary` and `userActorProjectWideScope`. Do not apply the Testcontainers requirement to replace these authorization decision seams.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-07-15T18:37:08.044Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-07-15T18:37:08.044Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-07-13T14:51:40.805Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-07-13T14:51:40.805Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use Vitest exclusively and never mock dependencies; use Testcontainers for integration dependencies.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-04-16T13:45:22.317Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3368
File: apps/webapp/test/engine/taskIdentifierRegistry.test.ts:3-19
Timestamp: 2026-04-16T13:45:22.317Z
Learning: In `apps/webapp/test/engine/taskIdentifierRegistry.test.ts`, the `vi.mock` calls for `~/services/taskIdentifierCache.server` (stubbing `getTaskIdentifiersFromCache` and `populateTaskIdentifierCache`), `~/models/task.server` (stubbing `getAllTaskIdentifiers`), and `~/db.server` (stubbing `prisma` and `$replica`) are intentional. The suite uses real Postgres via testcontainers for all `TaskIdentifier` DB operations, but isolates the Redis cache layer and legacy query fallback as separate concerns not exercised in this test file. Do not flag these mocks as violations of the no-mocks policy in future reviews.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-05-01T15:45:09.326Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3499
File: apps/webapp/test/auth-cross-cutting.e2e.full.test.ts:206-213
Timestamp: 2026-05-01T15:45:09.326Z
Learning: In `apps/webapp/test/auth-cross-cutting.e2e.full.test.ts`, the cross-environment JWT isolation test intentionally asserts `expect([401, 404]).toContain(res.status)` rather than a strict `expect(res.status).toBe(404)`. The dual-status assertion is deliberate: both 401 (auth rejected) and 404 (resource not found in the resolved env) prove the negative — that the JWT cannot access a resource scoped to a different environment. The loose assertion is kept so a planned change to the auth response code (e.g. returning 404 instead of 401 for cross-env mismatch) does not immediately break this test. The control case that proves the JWT itself is valid is covered by other tests in the same describe block.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-05-18T14:40:02.173Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3658
File: packages/core/src/v3/realtimeStreams/manager.test.ts:1-147
Timestamp: 2026-05-18T14:40:02.173Z
Learning: In the triggerdotdev/trigger.dev repo, the policy “Never mock anything — use testcontainers instead” should only be enforced for integration tests that interact with real external services (e.g., Redis, Postgres) via actual infrastructure. For unit tests that exercise pure in-memory logic (e.g., cache semantics) it is OK to stub collaborators such as `ApiClient` using Vitest (`vi.fn()`) to assert call counts or control behavior. Do not flag `vi.fn()`-based `ApiClient` stubs in unit tests as violations of the testcontainers policy.
Applied to files:
apps/webapp/app/presenters/v3/isSessionLive.test.ts
📚 Learning: 2026-03-22T19:34:22.737Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3187
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.errors._index/route.tsx:99-103
Timestamp: 2026-03-22T19:34:22.737Z
Learning: In `apps/webapp/app/presenters/v3/ErrorsListPresenter.server.ts`, the `statuses` filter (UNRESOLVED | RESOLVED | IGNORED) is applied in-memory after the ClickHouse query and a batch Postgres lookup via `getErrorGroupStates`. This is intentional: `status` lives in the Postgres `ErrorGroupState` table, not in ClickHouse, so post-query filtering is the correct approach. Do not flag this as a missing predicate or a no-op filter during code review.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.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/SessionListPresenter.server.ts
📚 Learning: 2026-04-20T15:08:59.789Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3417
File: apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts:27-40
Timestamp: 2026-04-20T15:08:59.789Z
Learning: In `apps/webapp/app/services/sessionsRepository/clickhouseSessionsRepository.server.ts`, the cursor predicate in `listSessionIds` compares only `session_id` while the `ORDER BY` clause uses `(created_at, session_id)`. This is intentional and consistent with the same pattern in `ClickHouseRunsRepository` and the waitpoints repository. Do not flag this as a skip/duplicate pagination bug in isolation — any fix must land across all three repositories at once as a shared follow-up.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-04-20T15:08:55.358Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3417
File: apps/webapp/app/services/sessionsReplicationService.server.ts:204-215
Timestamp: 2026-04-20T15:08:55.358Z
Learning: In `apps/webapp/app/services/sessionsReplicationService.server.ts` and `apps/webapp/app/services/runsReplicationService.server.ts`, the `getKey` function in `ConcurrentFlushScheduler` uses `${item.event}_${item.session.id}` / `${item.event}_${item.run.id}` respectively. This pattern is intentionally kept identical across both replication services for consistency. Any change to the deduplication key shape (e.g., keying solely by session/run id) must be applied to both services together, never to one service in isolation. Tracking as a cross-service follow-up.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-08-12T06:32:24.127Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 4577
File: apps/webapp/app/v3/services/createBackgroundWorker.server.ts:779-781
Timestamp: 2026-08-12T06:32:24.127Z
Learning: In `apps/webapp/app/v3/services/createBackgroundWorker.server.ts`, `syncDeclarativeSchedules` has a pre-existing non-serialized read/modify/delete reconciliation flow. Changes that reuse its initial schedule snapshot instead of a redundant re-fetch do not introduce this concurrency risk. Track reconciliation serialization separately from read-side performance changes unless a change modifies the synchronization boundary.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-12T20:51:21.099Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3936
File: packages/trigger-sdk/src/v3/ai.ts:0-0
Timestamp: 2026-06-12T20:51:21.099Z
Learning: In `packages/trigger-sdk/src/v3/ai.ts`, custom chat loops (`chat.customAgent` and `chat.createSession`) do not have a chat snapshot to consult for prior-state detection. For their `.in` resume-cursor seeding, it is valid to run the latest `turn-complete` cursor scan unconditionally on boot: fresh sessions have no `turn-complete` on `session.out`, so the scan returns no cursor and seeds nothing, while prior sessions are protected even if the continuation/attempt signal is missing.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-09T08:07:47.468Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 3499
File: apps/webapp/app/routes/api.v1.sessions.ts:49-55
Timestamp: 2026-05-09T08:07:47.468Z
Learning: In triggerdotdev/trigger.dev, the `GET /api/v1/sessions` route (`apps/webapp/app/routes/api.v1.sessions.ts`) has a known deferred security concern: when multiple `filter[taskIdentifier]` values are requested under a per-task-scoped JWT (`read:tasks:<id>`), `anyResource` OR semantics grant access but the repository then lists sessions for ALL requested task IDs, leaking data beyond the JWT's permitted scope. The fix (either a multi-task-filter → require `read:sessions` collection-scope guard at the `apiBuilder` level, or intersecting the filter with JWT-permitted task IDs before the repository call) requires surfacing permitted-task-IDs from `RbacAbility`, and is tracked for a separate PR as part of the broader `anyResource` semantics work.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-03T15:29:44.400Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3451
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardKey/route.tsx:248-254
Timestamp: 2026-05-03T15:29:44.400Z
Learning: In triggerdotdev/trigger.dev, the `hasAppliedFilters` / `hasFilters` flag that controls visibility of a "Clear all filters" button is intentionally scoped only to chip-style applied filters (e.g., tasks, queues, models, prompts, operations, providers). Persistent controls like `scope` and time range (`period`, `from`, `to`) have their own selectors that always render their current value, so they are deliberately excluded from this flag to avoid unexpected hidden side-effects when clearing. Do not flag this pattern as a bug or suggest adding scope/time checks to `hasAppliedFilters` in MetricDashboard or similar route components (e.g., `apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dashboards.$dashboardKey/route.tsx`).
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-07-27T15:07:29.887Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 4397
File: internal-packages/run-engine/src/engine/systems/batchSystem.ts:81-96
Timestamp: 2026-07-27T15:07:29.887Z
Learning: In `internal-packages/run-engine/src/engine/systems/batchSystem.ts`, `BatchSystem.#tryCompleteBatch` must guard its terminal completion update by excluding terminal statuses (`status: { notIn: ["ABORTED", "COMPLETED"] }`), rather than allow-listing `PENDING` or `PROCESSING`. Sealing transitions a batch to `PROCESSING`, and partial run-creation failures may use other legitimate non-terminal states; a `PENDING`-only guard leaves batchTriggerAndWait parent waitpoints unresolved.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-04-20T15:06:19.815Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3417
File: apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts:37-51
Timestamp: 2026-04-20T15:06:19.815Z
Learning: In `apps/webapp/app/routes/realtime.v1.sessions.$session.$io.ts` (and all session realtime read paths), `$replica` is intentionally used for the `resolveSessionByIdOrExternalId` call — including the `closedAt` guard in the PUT/initialize path. The project convention is to use `$replica` consistently across all session realtime routes. The race window (replica lag allowing a ghost-initialize after close) is accepted as not realistic in practice (clients follow the close API response; they do not race it). If replica lag ever causes issues, the mitigation is to revisit all realtime routes together, not to swap individual routes to `prisma`. Do not flag `$replica` usage in session realtime routes as a stale-read issue.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-14T16:39:02.759Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3546
File: packages/cli-v3/src/mcp/tools/agentChat.ts:29-29
Timestamp: 2026-05-14T16:39:02.759Z
Learning: In `packages/cli-v3/src/mcp/tools/agentChat.ts`, the `activeSessions` Map intentionally has no TTL, LRU eviction, or size limit. The MCP server is dev-only (`start_agent_chat` enforces `input.environment === "dev"`) and runs as a short-lived subprocess of the MCP host (Claude Code / Cursor / etc.). The process is restarted whenever the IDE restarts, so sessions never accumulate across host lifetimes. Do not flag the lack of automatic cleanup as a memory-leak risk in this file.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-04-23T19:03:13.105Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3417
File: packages/core/src/v3/schemas/api.ts:1556-1559
Timestamp: 2026-04-23T19:03:13.105Z
Learning: In `packages/core/src/v3/schemas/api.ts`, `CloseSessionRequestBody` intentionally uses `reason` (not `closedReason`) for the close request field. The asymmetry with the response/DB field `closedReason` is deliberate: request fields describe caller intent ("here is the reason I am closing"), response/DB fields describe stored state ("here is the reason this session was closed"). The `closed` prefix is considered tautological at call sites (e.g. `sessions.close(id, { reason: "..." })` reads more naturally than `sessions.close(id, { closedReason: "..." })`). Do not flag this naming asymmetry in future reviews.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-07-11T08:52:32.250Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 4234
File: apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts:159-164
Timestamp: 2026-07-11T08:52:32.250Z
Learning: In `apps/webapp/app/routes/realtime.v1.sessions.$session.$io.append.ts`, when the idempotency claim via `claimSessionStreamPart()` is lost (i.e., `wonClaim` is false, indicating a duplicate/retried append with the same client-supplied part id), the response omits `seq`, so the SDK's `appendInputChunk()` falls back to the legacy no-baseline behavior for turn-complete correlation in `packages/trigger-sdk/src/v3/chat.ts`. This is accepted as a known, narrow limitation (not a regression) rather than being fixed immediately: `claimSessionStreamPart()` currently only returns a boolean and cannot recover the already-committed seq. The tracked follow-up fix is to have the winner record the committed seq in the dedupe key so the loser can read it back on a lost claim.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-05-05T09:38:02.512Z
Learnt from: d-cs
Repo: triggerdotdev/trigger.dev PR: 3523
File: apps/webapp/app/routes/api.v3.batches.ts:178-181
Timestamp: 2026-05-05T09:38:02.512Z
Learning: When reviewing code that catches `ServiceValidationError` in `*.server.ts` files, do not blindly forward `error.status` to HTTP responses, because SVEs may be thrown with non-default statuses (e.g., 400/500) and forwarding them can cause client-visible behavioral regressions (e.g., surfacing 500s to clients). Prefer a safe default response status of `error.status ?? 422`, but only after confirming via the reachable call graph that the caught `ServiceValidationError` instances are expected to carry those non-default statuses; otherwise, normalize to `422` to avoid unexpected client-visible 5xx behavior.
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
📚 Learning: 2026-06-21T05:35:23.468Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 4005
File: apps/webapp/app/presenters/v3/ApiErrorListPresenter.server.ts:29-30
Timestamp: 2026-06-21T05:35:23.468Z
Learning: For triggerdotdev/trigger.dev list endpoints (and their presenters/handlers that implement list pagination), it is an established shared convention to allow both cursor query params `page[after]` and `page[before]` to be provided at the same time. When both are present, `page[before]` must take precedence (i.e., it should be used/wins). During code review, do NOT flag missing per-endpoint mutual-exclusion validation between `page[after]` and `page[before]` as a problem; if stricter enforcement is ever desired, it should be implemented as a codebase-wide shared convention (not individually per endpoint).
Applied to files:
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts
🔇 Additional comments (5)
apps/webapp/app/presenters/v3/isSessionLive.ts (2)
1-18: LGTM!
20-22: 🎯 Functional CorrectnessVerify that
PENDINGbelongs to the live state.Line 21 treats every non-final status as live.
apps/webapp/app/presenters/v3/isSessionLive.test.tslines 9-11 requirePENDINGto be live. The PR objective reserves Active and ticking duration for an executing run. IfPENDINGis a pre-execution state, classify it as Idle and do not tick its duration.apps/webapp/app/presenters/v3/isSessionLive.test.ts (1)
1-7: LGTM!Also applies to: 13-24
apps/webapp/app/presenters/v3/SessionListPresenter.server.ts (2)
17-17: LGTM!Also applies to: 215-227
250-255: 🎯 Functional CorrectnessVerify the dashboard display contract.
Verify the running Sessions dashboard with Chrome DevTools. Check Active, Idle, Closed, Expired, frozen completed duration, and the dash for sessions that never ran.
As per coding guidelines: “For dashboard changes, visually verify the running Remix app with Chrome DevTools MCP, using snapshots, screenshots, interaction, and console-message checks as appropriate.”
Source: Coding guidelines
Summary
Sessions on the list kept an ever-growing wall-clock duration even after their run had finished long ago, because the duration cell ticked for any open session. It now ticks only while a run is genuinely executing; otherwise it freezes at the last run's completion (or shows a dash if the session never ran). Live sessions still tick.
Notes
Session status is unchanged: it stays the filterable ACTIVE / CLOSED / EXPIRED set (an open session with no running run is still Active, because it is open). The fix is purely the duration. An earlier revision added a display-only Idle status; that was dropped because every status has to be filterable.
Also corrects the sessions.list docs, where the tag filter was described as matching triggerConfig.tags; it matches the session's own top-level tags.