Skip to content

feat(tasks): annotate GET /api/tasks/runs list runs with schedule title + email subject - #764

Open
sweetmantech wants to merge 3 commits into
mainfrom
feat/task-runs-titles
Open

feat(tasks): annotate GET /api/tasks/runs list runs with schedule title + email subject#764
sweetmantech wants to merge 3 commits into
mainfrom
feat/task-runs-titles

Conversation

@sweetmantech

@sweetmantech sweetmantech commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What

List mode of GET /api/tasks/runs now annotates each run with title: string | null — the originating scheduled task's scheduled_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 shares customer-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 schedule object exists only on single-run retrieve). The join is the reverse lookup the repo already uses in enrichTasks: for each of the account's scheduled_actions with a trigger_schedule_id, fetch its runs via filter[schedule] and map run id → title. Verified live: schedule-filtered run ids intersect the account-tag list exactly (run_cmr91tpc0… appears in both for sched_d4o3wm3xskug35izn59ej / "Weekly Social Media Health Check Report").

Per-schedule fetch uses the caller's limit, which is sufficient coverage: a schedule can contribute at most limit runs to the top-limit account list.

Implementation

  • lib/tasks/buildRunTitleMap.ts — run id → title map via per-schedule fetchTriggerRuns({ "filter[schedule]": … }); fails open per schedule
  • lib/tasks/attachRunTitles.tsselectScheduledActions({ account_id }) (existing lib/supabase function) → map → annotate; fails open to all-null titles so the runs list never 500s on title resolution
  • lib/tasks/getTaskRunHandler.ts — list branch pipes runs through attachRunTitles

One exported function per file; TDD RED→GREEN per unit (new modules failed collection + handler assertion failed before implementation).

Verification

  • Full suite: 3,950 passed / 715 files (baseline 3,940 + 10 new: 5 buildRunTitleMap, 4 attachRunTitles, 1 handler)
  • pnpm exec tsc --noEmit: 200 errors = exact pre-existing baseline, 0 new (measured by stash/compare)
  • pnpm lint clean, prettier clean
  • Live mechanism probe against Trigger.dev v1 + prod scheduled_actions (above)
  • Preview: unauthed probe pending deploy; authed list-mode pass (titles on real runs) pending a bearer — will comment results on this PR

Links

🤖 Generated with Claude Code


Summary by cubic

GET /api/tasks/runs list mode now returns two optional naming fields per run: title from the originating scheduled task and email_subject from the newest linked email. Previously names were not available; retrieve mode and all existing fields are unchanged. This makes chat rows distinguishable and prefers email_subject over title for display.

  • Resolves title: string|null by reverse-looking up each account scheduled_actions via Trigger.dev filter[schedule]; failures or unmapped runs yield null. Implemented with attachRunTitles + buildRunTitleMap.
  • Adds email_subject: string|null by querying the newest email_send_log row per trigger_run_id; lookup errors yield null. Implemented with attachRunEmailSubjects + selectEmailSubjectsByRunIds.
  • List handler now pipes runs through titles → email subjects; retrieve mode untouched. Types for email_send_log include subject and trigger_run_id.

Written for commit 01fb22b. Summary will update on new commits.

Review in cubic

…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>
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview Aug 14, 2026 12:22am

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sweetmantech, you've reached your PR review limit, so we couldn't start this review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 310b8525-c252-4e74-a4f2-ec801c36d378

📥 Commits

Reviewing files that changed from the base of the PR and between 6504303 and 01fb22b.

⛔ Files ignored due to path filters (5)
  • lib/tasks/__tests__/attachRunEmailSubjects.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/tasks/__tests__/attachRunTitles.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/tasks/__tests__/buildRunTitleMap.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • lib/tasks/__tests__/getTaskRunHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
  • types/database.types.ts is excluded by none and included by none
📒 Files selected for processing (5)
  • lib/supabase/email_send_log/selectEmailSubjectsByRunIds.ts
  • lib/tasks/attachRunEmailSubjects.ts
  • lib/tasks/attachRunTitles.ts
  • lib/tasks/buildRunTitleMap.ts
  • lib/tasks/getTaskRunHandler.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/task-runs-titles

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

❤️ Share

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 because attachRunTitles fetches 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}
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

{ "filter[tag]": `account:${validatedQuery.accountId}` },
validatedQuery.limit,
);
const runsWithTitles = await attachRunTitles(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

@sweetmantech

Copy link
Copy Markdown
Contributor Author

Preview verification — api-qogcgjdqy-recoup.vercel.app (head 8c05e164)

Verified on the preview (no bearer required):

  • GET /api/tasks/runs unauthenticated → 401 {"status":"error","error":"Exactly one of x-api-key or Authorization must be provided"} — auth-first, per contract
  • OPTIONS /api/tasks/runs200 (CORS preflight intact)
  • GET /api/tasks/runs?limit=0 unauthenticated → 401 (auth still precedes param validation; existing behavior unchanged)

Verified live against the real backing services (mechanism proof, done pre-implementation):

  • Trigger.dev v1 list-runs rows carry no schedule reference (raw response inspected on prod runs; schedule exists only on single-run retrieve) — confirming the reverse-lookup design
  • GET /api/v1/runs?filter[schedule]=sched_d4o3wm3xskug35izn59ej returns run_cmr91tpc0ic000on1wojxiyqv, which also appears in the filter[tag]=account:d7091248… list — the exact join this PR implements; that schedule's scheduled_actions.title is "Weekly Social Media Health Check Report", so that run will list with that title

Pending (stated plainly):

  • Authed list-mode pass on the preview (real runs carrying title) is blocked without a bearer: previews validate API keys against a different key salt than prod, and no Privy token is mintable headlessly here. Recommend the standard post-merge prod check: signed-in homepage run rows on chat.recoupable.dev once feat: run rows render the email subject or task title, generic label as fallback chat#1857 deploys (its fallback keeps rows rendering either way).

CI: test/lint/format all green; suite 3,950 passed; tsc at the exact 200-error pre-existing baseline (0 new).

🤖 Generated with Claude Code

sweetmantech and others added 2 commits August 13, 2026 19:15
… 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>
@sweetmantech

Copy link
Copy Markdown
Contributor Author

Revived for recoupable/chat#1958 (row 5), which absorbs the run-titles trio: merged a month of main in (clean), and extended list-mode annotation with email_subject — each run's newest linked email subject resolved via email_send_log.trigger_run_id (selectEmailSubjectsByRunIds, oldest-first so the newest send wins). Same fail-open stance as the title layer. Consumer display preference: email_subject ?? title ?? generic label.

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

@sweetmantech sweetmantech changed the title feat(tasks): thread scheduled task titles onto GET /api/tasks/runs list runs feat(tasks): annotate GET /api/tasks/runs list runs with schedule title + email subject Aug 14, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 issues found across 6 files (changes from recent commits).

Confidence score: 3/5

  • In lib/tasks/attachRunEmailSubjects.ts, repository-recorded emails never populate email_send_log.subject or trigger_run_id, so list runs can keep email_subject: null even 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 to status = "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 on created_at are 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 by trigger_run_id lack an account_id guard, 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-mode email_subject wiring in lib/tasks/__tests__/getTaskRunHandler.test.ts to 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));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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", () => ({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant