feat: handle ffmpeg-edit output in pollContentRuns#139
feat: handle ffmpeg-edit output in pollContentRuns#139recoup-coding-agent wants to merge 1 commit intomainfrom
Conversation
The ffmpeg-edit task returns `url` instead of `videoSourceUrl`. Add fallback to check `output.url` so the polling task can handle both create-content and ffmpeg-edit run outputs. Co-Authored-By: Paperclip <noreply@paperclip.ing>
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/content/pollContentRuns.ts`:
- Around line 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.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2ccca4a3-217b-4d0a-a2bc-6f145fe7202b
📒 Files selected for processing (1)
src/content/pollContentRuns.ts
| const output = run.output as { | ||
| videoSourceUrl?: string; | ||
| captionText?: string; | ||
| /** ffmpeg-edit tasks return `url` instead of `videoSourceUrl`. */ | ||
| url?: string; | ||
| } | null; |
There was a problem hiding this comment.
🧩 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.tsRepository: recoupable/tasks
Length of output: 99
🏁 Script executed:
cat -n src/content/pollContentRuns.tsRepository: recoupable/tasks
Length of output: 2253
🏁 Script executed:
cat -n src/content/sendContentCallback.tsRepository: recoupable/tasks
Length of output: 1792
🏁 Script executed:
cat -n src/tasks/pollContentRunTask.tsRepository: 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 -20Repository: 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 -10Repository: recoupable/tasks
Length of output: 397
🏁 Script executed:
# Check an example Zod schema in the project
head -50 src/recoup/fetchTask.tsRepository: 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/nullRepository: recoupable/tasks
Length of output: 212
🏁 Script executed:
cat -n src/content/__tests__/pollContentRuns.test.tsRepository: 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 2Repository: 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.
| 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.
Summary
pollContentRunsto checkoutput.url(from ffmpeg-edit tasks) in addition tooutput.videoSourceUrl(from create-content tasks)Context
Part of REC-68: when users reply in existing content threads, the bot now triggers ffmpeg-edit tasks. The polling task needs to extract the video URL from the edit task output which uses a different field name.
Test plan
videoSourceUrl)urlfallback)🤖 Generated with Claude Code
Summary by cubic
Update
pollContentRunsto fall back tooutput.url(fromffmpeg-edit) whenoutput.videoSourceUrlis missing, so polling works for both edit and create-content runs. This fulfills REC-68 by supporting replies that triggerffmpeg-edittasks.Written for commit d4a6fe9. Summary will update on new commits.
Summary by CodeRabbit
Release Notes