feat(tasks): annotate GET /api/tasks/runs list runs with schedule title + email subject - #764
feat(tasks): annotate GET /api/tasks/runs list runs with schedule title + email subject#764sweetmantech wants to merge 3 commits into
Conversation
…st runs Trigger.dev list runs carry only taskIdentifier (every scheduled prompt shares customer-prompt-task), so chat's homepage rendered identical "Scheduled Task" rows. List mode now annotates each run with the originating scheduled_actions.title, resolved by matching run ids against each schedule's runs (filter[schedule] on trigger_schedule_id — list runs expose no schedule reference, verified against the live v1 API). title is null when a run cannot be mapped; retrieve mode and all existing fields are unchanged. Fails open at both layers: a failed Trigger.dev schedule fetch skips that schedule's titles, a failed scheduled_actions lookup yields null titles — the runs list never breaks. Contract: recoupable/docs#268. Tracking: recoupable/chat#1850 (video-parity item 1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Warning Review limit reached
Next review available in: 113 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (5)
✨ 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 |
There was a problem hiding this comment.
1 issue found across 6 files
Confidence score: 3/5
- In
lib/tasks/getTaskRunHandler.ts, list-mode now does work proportional to all scheduled tasks becauseattachRunTitlesfetches every schedule and then calls Trigger.dev per schedule, so large accounts can see slow responses, higher backend load, or request timeouts even when clients ask for a small run limit — cap/batch title enrichment to the requested run limit (or otherwise bound per-request fan-out) before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/tasks/getTaskRunHandler.ts">
<violation number="1" location="lib/tasks/getTaskRunHandler.ts:32">
P2: List-mode requests now scale with the account's total scheduled task count, not the requested run limit, because `attachRunTitles` fetches every scheduled action and then one Trigger.dev run list per schedule. Consider bounding or batching this lookup so a large account cannot make `GET /api/tasks/runs` slow or trip Trigger.dev rate limits.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as Client (Next.js frontend)
participant Handler as getTaskRunHandler
participant Supabase as Supabase (scheduled_actions)
participant Trigger as Trigger.dev API
participant Attach as attachRunTitles
participant BuildMap as buildRunTitleMap
Client->>Handler: GET /api/tasks/runs?mode=list&accountId=acc_123&limit=20
Handler->>Handler: validate query (existing)
Note over Handler,Trigger: Existing: Fetch account-tagged runs
Handler->>Trigger: fetchTriggerRuns({filter[tag]: "account:acc_123"}, 20)
Trigger-->>Handler: runs[] (e.g., 2 runs)
Note over Handler,Attach: NEW: Annotate runs with titles
Handler->>Attach: attachRunTitles(runs, acc_123, 20)
alt Empty runs list (short circuit)
Attach->>Attach: return [] immediately
Attach-->>Handler: []
else Success path
Attach->>Supabase: selectScheduledActions({account_id: acc_123})
Supabase-->>Attach: actions[] (with trigger_schedule_id, title)
Attach->>BuildMap: buildRunTitleMap(actions, 20)
loop per action with trigger_schedule_id
BuildMap->>Trigger: fetchTriggerRuns({filter[schedule]: sched_id}, 20)
alt Fetch succeeded
Trigger-->>BuildMap: scheduleRuns[]
BuildMap->>BuildMap: map each run.id → action.title
else Fetch failed
BuildMap->>BuildMap: skip schedule (no entries)
end
end
BuildMap-->>Attach: Map<runId, title>
Attach->>Attach: runs.map(r => ({...r, title: map.get(r.id) ?? null}))
Attach-->>Handler: runsWithTitles[]
else Title resolution error (DB / Trigger.dev outage)
Attach->>Attach: log error, return runs with title: null
Attach-->>Handler: runsWithTitles[] (all null)
end
Handler-->>Client: 200 {status:"success", runs: runsWithTitles}
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| { "filter[tag]": `account:${validatedQuery.accountId}` }, | ||
| validatedQuery.limit, | ||
| ); | ||
| const runsWithTitles = await attachRunTitles( |
There was a problem hiding this comment.
P2: List-mode requests now scale with the account's total scheduled task count, not the requested run limit, because attachRunTitles fetches every scheduled action and then one Trigger.dev run list per schedule. Consider bounding or batching this lookup so a large account cannot make GET /api/tasks/runs slow or trip Trigger.dev rate limits.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/tasks/getTaskRunHandler.ts, line 32:
<comment>List-mode requests now scale with the account's total scheduled task count, not the requested run limit, because `attachRunTitles` fetches every scheduled action and then one Trigger.dev run list per schedule. Consider bounding or batching this lookup so a large account cannot make `GET /api/tasks/runs` slow or trip Trigger.dev rate limits.</comment>
<file context>
@@ -26,8 +29,13 @@ export async function getTaskRunHandler(request: NextRequest): Promise<NextRespo
{ "filter[tag]": `account:${validatedQuery.accountId}` },
validatedQuery.limit,
);
+ const runsWithTitles = await attachRunTitles(
+ runs,
+ validatedQuery.accountId,
</file context>
Preview verification —
|
… main chat#1958 row 5: merged a month of main into this branch and extended the read path — list mode now chains attachRunEmailSubjects after attachRunTitles, resolving each run's newest linked email subject from email_send_log.trigger_run_id (columns from database#57, written by api#833). Same fail-open stance as titles: a lookup failure yields null subjects, never a broken runs list. Display preference for consumers: email_subject ?? title ?? generic label. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Revived for recoupable/chat#1958 (row 5), which absorbs the run-titles trio: merged a month of Depends on database#57 (columns) and pairs with api#833 (write path). Tests: 89 green in lib/tasks incl. 3 new for the annotator; handler test extended. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
5 issues found across 6 files (changes from recent commits).
Confidence score: 3/5
- In
lib/tasks/attachRunEmailSubjects.ts, repository-recorded emails never populateemail_send_log.subjectortrigger_run_id, so list runs can keepemail_subject: nulleven when emails were sent; this makes the feature appear broken for users — persist subject and run linkage at write time. - In
lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, selecting newest rows without filtering tostatus = "sent"can attach subjects from failed/rejected attempts, which misrepresents delivery outcomes in run history — restrict the query to sent records before choosing the latest subject. - In
lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, ties oncreated_atare nondeterministic, so different subjects can be chosen across runs/environments; this creates flaky, hard-to-debug UI results — add a deterministic secondary sort key (for example by id). - In
lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, lookups bytrigger_run_idlack anaccount_idguard, which is safe with current callers but leaves room for cross-account subject exposure if reused incorrectly; add account scoping in the query, and cover list-modeemail_subjectwiring inlib/tasks/__tests__/getTaskRunHandler.test.tsto catch regressions.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/tasks/attachRunEmailSubjects.ts">
<violation number="1" location="lib/tasks/attachRunEmailSubjects.ts:26">
P2: For emails recorded by this repository, this lookup returns no subject because `email_send_log` never receives `subject` or `trigger_run_id`; list runs therefore keep `email_subject: null`. Persist the email subject and originating Trigger run ID in the email logging path before relying on this annotation.</violation>
</file>
<file name="lib/tasks/__tests__/getTaskRunHandler.test.ts">
<violation number="1" location="lib/tasks/__tests__/getTaskRunHandler.test.ts:19">
P3: The new `attachRunEmailSubjects` mock defines a real annotation behavior (`run_mail` → `"Weekly report"`) but no list-mode handler test uses `run_mail` or asserts `email_subject`, so the new feature's wiring through the handler is unverified. Every existing list test would still pass if `getTaskRunHandler` stopped calling `attachRunEmailSubjects` entirely (e.g., the `toEqual({ status: "success", runs: [] })` and field-level assertions don't reference `email_subject`). Add a list-mode test that asserts the mocked annotation flows into the response, exercising the `run_mail` special case, so the integration path is locked down.</violation>
</file>
<file name="lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts">
<violation number="1" location="lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts:19">
P2: selectEmailSubjectsByRunIds reads email_send_log by `trigger_run_id` with no `account_id` filter, so it can resolve email subjects for any run id. Current callers only pass the authenticated account's own run ids, but this exported reader diverges from the account-scoped `selectEmailSendLog` convention. Scope the query to the account to keep email-subject reads isolated.</violation>
<violation number="2" location="lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts:20">
P2: When a linked attempt has `status = "send_failed"` or `"rejected"`, this query can label a run with an email that was never sent. Filter `email_send_log` to `status = "sent"` before selecting the newest subject.</violation>
<violation number="3" location="lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts:21">
P2: When two linked rows share `created_at`, the database does not guarantee their relative order, so `Map.set` can retain either subject instead of a stable newest-send result. Add a deterministic secondary order such as `id`.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
|
|
||
| try { | ||
| const subjectByRunId = await selectEmailSubjectsByRunIds(runs.map(run => run.id)); |
There was a problem hiding this comment.
P2: For emails recorded by this repository, this lookup returns no subject because email_send_log never receives subject or trigger_run_id; list runs therefore keep email_subject: null. Persist the email subject and originating Trigger run ID in the email logging path before relying on this annotation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/tasks/attachRunEmailSubjects.ts, line 26:
<comment>For emails recorded by this repository, this lookup returns no subject because `email_send_log` never receives `subject` or `trigger_run_id`; list runs therefore keep `email_subject: null`. Persist the email subject and originating Trigger run ID in the email logging path before relying on this annotation.</comment>
<file context>
@@ -0,0 +1,32 @@
+ }
+
+ try {
+ const subjectByRunId = await selectEmailSubjectsByRunIds(runs.map(run => run.id));
+ return runs.map(run => ({ ...run, email_subject: subjectByRunId.get(run.id) ?? null }));
+ } catch (error) {
</file context>
| .select("trigger_run_id, subject, created_at") | ||
| .in("trigger_run_id", runIds) | ||
| .not("subject", "is", null) | ||
| .order("created_at", { ascending: true }); |
There was a problem hiding this comment.
P2: When two linked rows share created_at, the database does not guarantee their relative order, so Map.set can retain either subject instead of a stable newest-send result. Add a deterministic secondary order such as id.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, line 21:
<comment>When two linked rows share `created_at`, the database does not guarantee their relative order, so `Map.set` can retain either subject instead of a stable newest-send result. Add a deterministic secondary order such as `id`.</comment>
<file context>
@@ -0,0 +1,34 @@
+ .select("trigger_run_id, subject, created_at")
+ .in("trigger_run_id", runIds)
+ .not("subject", "is", null)
+ .order("created_at", { ascending: true });
+
+ if (error) {
</file context>
| .from("email_send_log") | ||
| .select("trigger_run_id, subject, created_at") | ||
| .in("trigger_run_id", runIds) | ||
| .not("subject", "is", null) |
There was a problem hiding this comment.
P2: When a linked attempt has status = "send_failed" or "rejected", this query can label a run with an email that was never sent. Filter email_send_log to status = "sent" before selecting the newest subject.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, line 20:
<comment>When a linked attempt has `status = "send_failed"` or `"rejected"`, this query can label a run with an email that was never sent. Filter `email_send_log` to `status = "sent"` before selecting the newest subject.</comment>
<file context>
@@ -0,0 +1,34 @@
+ .from("email_send_log")
+ .select("trigger_run_id, subject, created_at")
+ .in("trigger_run_id", runIds)
+ .not("subject", "is", null)
+ .order("created_at", { ascending: true });
+
</file context>
| const { data, error } = await supabase | ||
| .from("email_send_log") | ||
| .select("trigger_run_id, subject, created_at") | ||
| .in("trigger_run_id", runIds) |
There was a problem hiding this comment.
P2: selectEmailSubjectsByRunIds reads email_send_log by trigger_run_id with no account_id filter, so it can resolve email subjects for any run id. Current callers only pass the authenticated account's own run ids, but this exported reader diverges from the account-scoped selectEmailSendLog convention. Scope the query to the account to keep email-subject reads isolated.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts, line 19:
<comment>selectEmailSubjectsByRunIds reads email_send_log by `trigger_run_id` with no `account_id` filter, so it can resolve email subjects for any run id. Current callers only pass the authenticated account's own run ids, but this exported reader diverges from the account-scoped `selectEmailSendLog` convention. Scope the query to the account to keep email-subject reads isolated.</comment>
<file context>
@@ -0,0 +1,34 @@
+ const { data, error } = await supabase
+ .from("email_send_log")
+ .select("trigger_run_id, subject, created_at")
+ .in("trigger_run_id", runIds)
+ .not("subject", "is", null)
+ .order("created_at", { ascending: true });
</file context>
| attachRunTitles: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock("../attachRunEmailSubjects", () => ({ |
There was a problem hiding this comment.
P3: The new attachRunEmailSubjects mock defines a real annotation behavior (run_mail → "Weekly report") but no list-mode handler test uses run_mail or asserts email_subject, so the new feature's wiring through the handler is unverified. Every existing list test would still pass if getTaskRunHandler stopped calling attachRunEmailSubjects entirely (e.g., the toEqual({ status: "success", runs: [] }) and field-level assertions don't reference email_subject). Add a list-mode test that asserts the mocked annotation flows into the response, exercising the run_mail special case, so the integration path is locked down.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/tasks/__tests__/getTaskRunHandler.test.ts, line 19:
<comment>The new `attachRunEmailSubjects` mock defines a real annotation behavior (`run_mail` → `"Weekly report"`) but no list-mode handler test uses `run_mail` or asserts `email_subject`, so the new feature's wiring through the handler is unverified. Every existing list test would still pass if `getTaskRunHandler` stopped calling `attachRunEmailSubjects` entirely (e.g., the `toEqual({ status: "success", runs: [] })` and field-level assertions don't reference `email_subject`). Add a list-mode test that asserts the mocked annotation flows into the response, exercising the `run_mail` special case, so the integration path is locked down.</comment>
<file context>
@@ -16,6 +16,13 @@ vi.mock("../attachRunTitles", () => ({
attachRunTitles: vi.fn(),
}));
+vi.mock("../attachRunEmailSubjects", () => ({
+ // chat#1958: pass-through that mirrors the fail-open null default.
+ attachRunEmailSubjects: vi.fn(async (runs: { id: string }[]) =>
</file context>
What
List mode of
GET /api/tasks/runsnow annotates each run withtitle: string | null— the originating scheduled task'sscheduled_actions.title, null when unresolvable. Retrieve mode (runId) and every existing field are unchanged; the addition is purely additive.Why
Trigger.dev list runs carry only
taskIdentifier, and every scheduled prompt sharescustomer-prompt-task— so the chat homepage renders five identical "Scheduled Task" rows (recoupable/chat#1850, video-parity item 1, promoted from the chat#1853 fast-follow note).How runs link to schedules (mechanism)
Trigger.dev's v1 list-runs rows expose no schedule reference (verified against the live API; the
scheduleobject exists only on single-run retrieve). The join is the reverse lookup the repo already uses inenrichTasks: for each of the account'sscheduled_actionswith atrigger_schedule_id, fetch its runs viafilter[schedule]and map run id → title. Verified live: schedule-filtered run ids intersect the account-tag list exactly (run_cmr91tpc0…appears in both forsched_d4o3wm3xskug35izn59ej/ "Weekly Social Media Health Check Report").Per-schedule fetch uses the caller's
limit, which is sufficient coverage: a schedule can contribute at mostlimitruns to the top-limitaccount list.Implementation
lib/tasks/buildRunTitleMap.ts— run id → title map via per-schedulefetchTriggerRuns({ "filter[schedule]": … }); fails open per schedulelib/tasks/attachRunTitles.ts—selectScheduledActions({ account_id })(existing lib/supabase function) → map → annotate; fails open to all-null titles so the runs list never 500s on title resolutionlib/tasks/getTaskRunHandler.ts— list branch pipes runs throughattachRunTitlesOne exported function per file; TDD RED→GREEN per unit (new modules failed collection + handler assertion failed before implementation).
Verification
pnpm exec tsc --noEmit: 200 errors = exact pre-existing baseline, 0 new (measured by stash/compare)pnpm lintclean, prettier cleanscheduled_actions(above)Links
feat/home-run-titles(rendersrun.titleinHomeRunRowwith today's label as fallback)🤖 Generated with Claude Code
Summary by cubic
GET
/api/tasks/runslist mode now returns two optional naming fields per run:titlefrom the originating scheduled task andemail_subjectfrom the newest linked email. Previously names were not available; retrieve mode and all existing fields are unchanged. This makes chat rows distinguishable and prefersemail_subjectovertitlefor display.title: string|nullby reverse-looking up each accountscheduled_actionsvia Trigger.devfilter[schedule]; failures or unmapped runs yieldnull. Implemented withattachRunTitles+buildRunTitleMap.email_subject: string|nullby querying the newestemail_send_logrow pertrigger_run_id; lookup errors yieldnull. Implemented withattachRunEmailSubjects+selectEmailSubjectsByRunIds.email_send_logincludesubjectandtrigger_run_id.Written for commit 01fb22b. Summary will update on new commits.