Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/content/pollContentRuns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ export async function pollContentRuns(
const output = run.output as {
videoSourceUrl?: string;
captionText?: string;
/** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */
url?: string;
} | null;
Comment on lines 42 to 47

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this file is currently using a type assertion for run.output
# and does not validate with Zod.
rg -n --type=ts 'run\.output\s+as\s+\{' src/content/pollContentRuns.ts
rg -n --type=ts 'from ["'"'"']zod["'"'"']|z\.object\(|safeParse\(' src/content/pollContentRuns.ts

Repository: recoupable/tasks

Length of output: 99


🏁 Script executed:

cat -n src/content/pollContentRuns.ts

Repository: recoupable/tasks

Length of output: 2253


🏁 Script executed:

cat -n src/content/sendContentCallback.ts

Repository: recoupable/tasks

Length of output: 1792


🏁 Script executed:

cat -n src/tasks/pollContentRunTask.ts

Repository: recoupable/tasks

Length of output: 1374


🏁 Script executed:

# Check how run.output is typed in the SDK
rg -n "runs\.poll\(" src/ -A 5 | head -20

Repository: recoupable/tasks

Length of output: 1241


🏁 Script executed:

# Check if Zod is available in the project dependencies
cat package.json | grep -A 5 -B 5 "zod"

Repository: recoupable/tasks

Length of output: 477


🏁 Script executed:

# Check for other schema definitions using Zod in the project
find src -name "*.ts" -type f | xargs grep -l "z.object\|z.string" | head -10

Repository: recoupable/tasks

Length of output: 397


🏁 Script executed:

# Check an example Zod schema in the project
head -50 src/recoup/fetchTask.ts

Repository: recoupable/tasks

Length of output: 1417


🏁 Script executed:

# Check if there are any tests for pollContentRuns to understand expected behavior
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "pollContentRuns" 2>/dev/null

Repository: recoupable/tasks

Length of output: 212


🏁 Script executed:

cat -n src/content/__tests__/pollContentRuns.test.ts

Repository: recoupable/tasks

Length of output: 4345


🏁 Script executed:

# Verify the learnings about result.ok check for triggerAndWait
rg -n "triggerAndWait\|runs\.poll" src/content/pollContentRuns.ts -B 2 -A 2

Repository: recoupable/tasks

Length of output: 42


Validate run output with Zod schema and require a URL before returning completed.

Lines 42–47 and 53 use a type assertion on run.output without schema validation. When both videoSourceUrl and url are absent or null, the code still returns status: "completed" with videoUrl: undefined, which can misrepresent success to external callbacks.

Per coding guidelines, "Use Zod for schema validation" for TypeScript files. Add a Zod schema with a refine() check to ensure at least one video URL field is present before marking the run as completed.

Proposed fix
 import { runs } from "@trigger.dev/sdk/v3";
+import { z } from "zod";
 import { logStep } from "../sandboxes/logStep";
 
 const POLL_INTERVAL_MS = 30_000;
+const ContentRunOutputSchema = z
+  .object({
+    videoSourceUrl: z.string().url().optional(),
+    /** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */
+    url: z.string().url().optional(),
+    captionText: z.string().optional(),
+  })
+  .refine(output => Boolean(output.videoSourceUrl ?? output.url), {
+    message: "Missing video URL in run output",
+  });
 
 export type ContentRunResult = {
   runId: string;
   status: "completed" | "failed" | "timeout";
   videoUrl?: string;
   captionText?: string;
   error?: string;
 };
 
 /**
  * Waits for all Trigger.dev create-content runs to reach a terminal state
  * using the native runs.poll() function, then maps results.
  */
 export async function pollContentRuns(
   runIds: string[],
 ): Promise<ContentRunResult[]> {
   const settled = await Promise.allSettled(
     runIds.map(runId =>
       runs.poll(runId, { pollIntervalMs: POLL_INTERVAL_MS }),
     ),
   );
 
   return settled.map((result, i) => {
     const runId = runIds[i];
 
     if (result.status === "rejected") {
       logStep(`Run poll failed: ${runId}`, false, { runId, error: result.reason });
       return {
         runId,
         status: "failed" as const,
         error: result.reason?.message ?? "Unknown error",
       };
     }
 
     const run = result.value;
 
     if (run.status === "COMPLETED") {
-      const output = run.output as {
-        videoSourceUrl?: string;
-        captionText?: string;
-        /** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */
-        url?: string;
-      } | null;
+      const parsedOutput = ContentRunOutputSchema.safeParse(run.output ?? {});
+      if (!parsedOutput.success) {
+        logStep(`Run completed with invalid output: ${runId}`, false, {
+          runId,
+          issues: parsedOutput.error.issues,
+        });
+        return {
+          runId,
+          status: "failed" as const,
+          error: "Completed run missing valid output URL",
+        };
+      }
+      const output = parsedOutput.data;
 
       logStep(`Run completed: ${runId}`, false, { runId });
       return {
         runId,
         status: "completed" as const,
-        videoUrl: output?.videoSourceUrl ?? output?.url,
-        captionText: output?.captionText,
+        videoUrl: output.videoSourceUrl ?? output.url,
+        captionText: output.captionText,
       };
     }
📝 Committable suggestion

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

Suggested change
const output = run.output as {
videoSourceUrl?: string;
captionText?: string;
/** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */
url?: string;
} | null;
import { runs } from "@trigger.dev/sdk/v3";
import { z } from "zod";
import { logStep } from "../sandboxes/logStep";
const POLL_INTERVAL_MS = 30_000;
const ContentRunOutputSchema = z
.object({
videoSourceUrl: z.string().url().optional(),
/** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */
url: z.string().url().optional(),
captionText: z.string().optional(),
})
.refine(output => Boolean(output.videoSourceUrl ?? output.url), {
message: "Missing video URL in run output",
});
export type ContentRunResult = {
runId: string;
status: "completed" | "failed" | "timeout";
videoUrl?: string;
captionText?: string;
error?: string;
};
/**
* Waits for all Trigger.dev create-content runs to reach a terminal state
* using the native runs.poll() function, then maps results.
*/
export async function pollContentRuns(
runIds: string[],
): Promise<ContentRunResult[]> {
const settled = await Promise.allSettled(
runIds.map(runId =>
runs.poll(runId, { pollIntervalMs: POLL_INTERVAL_MS }),
),
);
return settled.map((result, i) => {
const runId = runIds[i];
if (result.status === "rejected") {
logStep(`Run poll failed: ${runId}`, false, { runId, error: result.reason });
return {
runId,
status: "failed" as const,
error: result.reason?.message ?? "Unknown error",
};
}
const run = result.value;
if (run.status === "COMPLETED") {
const parsedOutput = ContentRunOutputSchema.safeParse(run.output ?? {});
if (!parsedOutput.success) {
logStep(`Run completed with invalid output: ${runId}`, false, {
runId,
issues: parsedOutput.error.issues,
});
return {
runId,
status: "failed" as const,
error: "Completed run missing valid output URL",
};
}
const output = parsedOutput.data;
logStep(`Run completed: ${runId}`, false, { runId });
return {
runId,
status: "completed" as const,
videoUrl: output.videoSourceUrl ?? output.url,
captionText: output.captionText,
};
}
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/content/pollContentRuns.ts` around lines 42 - 47, The code reads
run.output into the local const output via a type assertion but never validates
it; add a Zod schema (e.g., OutputSchema) that defines videoSourceUrl?: string,
url?: string, captionText?: string and uses refine() to require that at least
one of videoSourceUrl or url is present, then parse/validate run.output with
OutputSchema.parse or safeParse before treating the run as completed; if
validation fails, do not return status: "completed" (handle as still
pending/errored) and only set videoUrl from the validated output (prefer
videoSourceUrl or fallback to url) when the schema passes. Ensure these changes
touch the same identifiers: the const output assignment, any logic that returns
status: "completed", and the code that reads videoUrl/captionText.


logStep(`Run completed: ${runId}`, false, { runId });
return {
runId,
status: "completed" as const,
videoUrl: output?.videoSourceUrl,
videoUrl: output?.videoSourceUrl ?? output?.url,
captionText: output?.captionText,
};
}
Expand Down
Loading