You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
#1199 stage 10: export_timeline - an OpenTimelineIO bundle of a joined piece #1211
Stage of #1199, filed by model claude-opus-5-5 via provider anthropic. Already
built on feat/long-form-media (merged 3f8ed222). The authoritative spec is the
plan (docs/superpowers/plans/2026-11-03-long-form-media.md, task named below) and
the design record. If they differ, the design record says what shipped.
Built: the export_timeline task (dw/tasks/timeline_export.py; 1e77425, 75423d4, 224fb27). video (a stored file) plus optional sources and shots give
an .otioz (application/x-otioz) with one clip per shot, gaps filled, and an audio
track when the file has one. Paths are validated, and member names are media/<basename>.
Observe over MCP:
run_workflow an inline workflow: templates/assemble-and-score (or any joined
piece), then an export_timeline task step on its output.
download_output the .otioz and unzip it.
Acceptance intent:
The bundle's timeline has one clip per shot, in order, with durations that sum to
the piece.
The media are inside the bundle.
An in-memory clip is refused.
A shot list that does not match the video is refused (224fb27).
Resolve 21 imports the bundle (Don, 2026-11-06). The Mac App Store build is sandboxed:
from ~/Movies/DaVinci Resolve/ it works, from ~/Desktop it silently does nothing.
That is not a tester case.
Verify of #1211 by tester claude-opus-5-5 via anthropic, against lem develop @ 85bcd33. Bounced to the lead: one of the four acceptance points fails.
The suites have no pending: #1211 cases (I grepped every regression-suite-*.md), so I checked the issue's acceptance intent directly. The input was the 4-shot joined H3 take output:templates/minimax/chained-segments/20261107-145804-eb6cf5f0/final/MiniMaxH3I2VChained-chained_keyframe_to_video_audio.0-0.0.mp4. It has 430 frames at 24 fps and stereo audio. get_gallery_metadata lists its media.shots as 124 + 102 + 102 + 102 frames. Every run below was an inline one-step export_timeline workflow with result.content_type: application/x-otioz, run in workspace="default".
#
export_timeline arguments
Result
Expected
1
video only (shots from the manifest), name: "t1211"
succeeded, .otioz 3.9 MB
ok
2
video: "previous_result:frames" (the output of a video_frames step)
failed: the video must be a stored video (asset:, output: or a path) - the bundle carries the file itself, so a clip still in memory has to be saved first, not FittedVideo
ok, refused
3
shots: [{name:a,start_frame:0,num_frames:124},{name:b,start_frame:124,num_frames:400}]. The shots total 524 frames against a 430-frame video.
succeeded, wrote a 3.9 MB .otioz, no warnings in job.warnings or get_job_events
should be refused
4
shots: [{name:a,start_frame:0,num_frames:124},{name:b,start_frame:600,num_frames:100}]. Shot b starts 170 frames past the end of the video.
succeeded, no warnings
should be refused
5
sources: 2 segment files for the 4 manifest shots
failed: sources names 2 files for 4 shots - give one per shot, in order
ok, refused
What fails: "A shot list that does not match the video is refused (224fb27)". The task checks the number of sources against the number of shots (run 5), but it doesn't check a caller's shots against the video's length. A shot can overrun the end (run 3) or start past it (run 4), and the task still writes a bundle without a word. I used the same record keys that media.shots reports (name, start_frame, num_frames). If shots expects a different key shape, the task should refuse unknown records rather than accept them.
What I couldn't check over MCP: "one clip per shot, in order, with durations that sum to the piece" and "the media are inside the bundle". On this endpoint download_output saves on the server, get_output_text refuses the bundle (it reports it as application/octet-stream, not text), and get_gallery_metadata returns media: null for it. No consumer tool shows what is inside the .otioz. The 3.9 MB size is close to the 4.1 MB source, which suggests the media is embedded, but that is an inference, not a check. Two ways to make this verifiable: a timeline summary on the job or in get_gallery_metadata (clip names and durations, member list), or a regression case in a later stage that reads one.
unstrand: Don — this is a stage of #1199 (its body says "Stage of #1199"), but GitHub has no sub-issue parent link on it, so lib/classify.jq reads "stage with no parent" and no queue picks it up. Labels alone can't fix that: the link needs a sub-issue write, which this loop can't make. Separately, the tester bounced it to the lead with a failed acceptance point, and the parent #1199 sits with owner:tester + status:fixed-pending-verify, so even linked it would wait on #1199 rather than reach a lead build.
B, the bounce is a plain bug for the fix loop: drop stage, add bug, and set owner:implementer with no status (the target:lem claim stays; add a backend: label).
Unstranded by model claude-opus-5-5 via provider anthropic.
Decision for Don: do the bounced #1199 stages go back to the lead (link them under #1199) or to the implementer loop as bugs? Kind: record Default: reading A: link each as a sub-issue of #1199, return #1199 and the stages to owner:lead If otherwise: reading B: relabel each bug + owner:implementer, no status, with a backend: label Reversible: yes
Don's ruling on the unstrand question: reading B. The bounce goes to the fix loop as a plain bug, not stage rework. Relabelled bug + owner:implementer + backend:shared, with no status and no stage. The target:lem claim stays. The tester's bounce comment above is the spec. If it hides a spec question (whether the threshold or the acceptance claim is wrong, or whether an effect is intended), settle it from the design record and the bench records, and escalate only if those don't answer it.
— recorded from Don's Claude Code session (model claude-opus-5-5 via provider anthropic)
triage: work — export_timeline accepts a shot list totalling 524 frames on a 430-frame video; the 224fb27 mismatch check does not fire. Self-contained in dw/tasks/timeline_export.py. Triaged by model claude-opus-5-5 via provider anthropic.
Fix by implementer claude-sonnet-5-5 via anthropic. export_timeline (video mode) now refuses a shot list that runs past the video. Your run 3 (shots 0-124 and 124-524 on a 430-frame video) and run 4 (shot starting at 600) now fail with shot 'b' runs to frame N (...) but the video has only 430 frames - the shots must fit inside the video, where before both wrote a bundle. Valid shots, the manifest default and the overlap and sources count checks are unchanged. The check applies to the video-mode layout only: in sources mode each clip uses its own file's length, so the shot spans there are names only. Commit 2fc7c80 on develop, deployed to lem 19:40 (healthy).
Not changed: the tester's other note, that no consumer tool shows what is inside the .otioz. That is a separate visibility gap and I did not address it here.
Architecture review queued by the loop driver: this hand-off changed dw/ or dw_mcp/ on develop (1ee08ea07b..2fc7c8010a: dw/tasks/timeline_export.py). The reviewer checks it against docs/ARCHITECTURE.md before the tester verifies.
Architecture review of 2fc7c80 (1ee08ea..2fc7c80, dw/tasks/timeline_export.py, the only file touched) by reviewer claude-opus-5-5 via anthropic: pass, no findings. Read against docs/ARCHITECTURE.md and stage-c-guardrails.md item 3. Second owner: the new bounds check (shot start_frame + num_frames <= the video's frame_count) sits next to this module's own _refuse_overlaps. No map row assigns caller-shot-list validation to another module: the 'Shots in a joined video' row (dw/shots.py) covers AudioVideo constructors and measured sample spans, and dw/shots.py has no bounds helper. The frame count comes from the existing _facts(path) call, so the change adds no media decode. Build vs buy: it is integer comparison, so there is nothing a pyproject dependency would replace. Map: no new module or owner, so no row is needed. Context: no CLAUDE.md change. The issue has no arch-approved label, and nothing needed waiving. Left with owner:tester + status:fixed-pending-verify for the MCP verify.
Activity
dkackman commentedon Nov 7, 2026
Verify of #1211 by tester
claude-opus-5-5viaanthropic, against lem develop @ 85bcd33. Bounced to the lead: one of the four acceptance points fails.The suites have no
pending: #1211cases (I grepped everyregression-suite-*.md), so I checked the issue's acceptance intent directly. The input was the 4-shot joined H3 takeoutput:templates/minimax/chained-segments/20261107-145804-eb6cf5f0/final/MiniMaxH3I2VChained-chained_keyframe_to_video_audio.0-0.0.mp4. It has 430 frames at 24 fps and stereo audio.get_gallery_metadatalists itsmedia.shotsas 124 + 102 + 102 + 102 frames. Every run below was an inline one-stepexport_timelineworkflow withresult.content_type: application/x-otioz, run inworkspace="default".export_timelineargumentsvideoonly (shots from the manifest),name: "t1211".otioz3.9 MBvideo: "previous_result:frames"(the output of avideo_framesstep)the video must be a stored video (asset:, output: or a path) - the bundle carries the file itself, so a clip still in memory has to be saved first, not FittedVideoshots: [{name:a,start_frame:0,num_frames:124},{name:b,start_frame:124,num_frames:400}]. The shots total 524 frames against a 430-frame video..otioz, no warnings injob.warningsorget_job_eventsshots: [{name:a,start_frame:0,num_frames:124},{name:b,start_frame:600,num_frames:100}]. Shot b starts 170 frames past the end of the video.sources: 2 segment files for the 4 manifest shotssources names 2 files for 4 shots - give one per shot, in orderWhat fails: "A shot list that does not match the video is refused (224fb27)". The task checks the number of
sourcesagainst the number of shots (run 5), but it doesn't check a caller'sshotsagainst the video's length. A shot can overrun the end (run 3) or start past it (run 4), and the task still writes a bundle without a word. I used the same record keys thatmedia.shotsreports (name,start_frame,num_frames). Ifshotsexpects a different key shape, the task should refuse unknown records rather than accept them.What I couldn't check over MCP: "one clip per shot, in order, with durations that sum to the piece" and "the media are inside the bundle". On this endpoint
download_outputsaves on the server,get_output_textrefuses the bundle (it reports it asapplication/octet-stream, not text), andget_gallery_metadatareturnsmedia: nullfor it. No consumer tool shows what is inside the.otioz. The 3.9 MB size is close to the 4.1 MB source, which suggests the media is embedded, but that is an inference, not a check. Two ways to make this verifiable: a timeline summary on the job or inget_gallery_metadata(clip names and durations, member list), or a regression case in a later stage that reads one.Cleanup: all five runs are deleted.
dkackman commentedon Nov 7, 2026
unstrand: Don — this is a stage of #1199 (its body says "Stage of #1199"), but GitHub has no sub-issue parent link on it, so lib/classify.jq reads "stage with no parent" and no queue picks it up. Labels alone can't fix that: the link needs a sub-issue write, which this loop can't make. Separately, the tester bounced it to the lead with a failed acceptance point, and the parent #1199 sits with
owner:tester+status:fixed-pending-verify, so even linked it would wait on #1199 rather than reach a lead build.Readings:
owner:lead, and keep thisowner:lead+stage.stage, addbug, and setowner:implementerwith no status (thetarget:lemclaim stays; add abackend:label).Same call for #1205, #1206, #1207, #1208, #1209, #1211 and #1214.
Unstranded by model
claude-opus-5-5via provideranthropic.Decision for Don: do the bounced #1199 stages go back to the lead (link them under #1199) or to the implementer loop as bugs?
Kind: record Default: reading A: link each as a sub-issue of #1199, return #1199 and the stages to
owner:leadIf otherwise: reading B: relabel each
bug+owner:implementer, no status, with abackend:label Reversible: yesdkackman commentedon Nov 7, 2026
Don's ruling on the unstrand question: reading B. The bounce goes to the fix loop as a plain bug, not stage rework. Relabelled
bug+owner:implementer+backend:shared, with no status and nostage. Thetarget:lemclaim stays. The tester's bounce comment above is the spec. If it hides a spec question (whether the threshold or the acceptance claim is wrong, or whether an effect is intended), settle it from the design record and the bench records, and escalate only if those don't answer it.— recorded from Don's Claude Code session (model
claude-opus-5-5via provideranthropic)dkackman commentedon Nov 7, 2026
triage: work —
export_timelineaccepts a shot list totalling 524 frames on a 430-frame video; the 224fb27 mismatch check does not fire. Self-contained indw/tasks/timeline_export.py. Triaged by modelclaude-opus-5-5via provideranthropic.fix(timeline): #1211 - export_timeline refuses shots that run past th…
dkackman commentedon Nov 7, 2026
Fix by implementer
claude-sonnet-5-5viaanthropic.export_timeline(video mode) now refuses a shot list that runs past the video. Your run 3 (shots 0-124 and 124-524 on a 430-frame video) and run 4 (shot starting at 600) now fail withshot 'b' runs to frame N (...) but the video has only 430 frames - the shots must fit inside the video, where before both wrote a bundle. Valid shots, the manifest default and the overlap andsourcescount checks are unchanged. The check applies to the video-mode layout only: insourcesmode each clip uses its own file's length, so the shot spans there are names only. Commit 2fc7c80 on develop, deployed to lem 19:40 (healthy).Not changed: the tester's other note, that no consumer tool shows what is inside the
.otioz. That is a separate visibility gap and I did not address it here.dkackman commentedon Nov 7, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(1ee08ea07b..2fc7c8010a: dw/tasks/timeline_export.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Nov 7, 2026
Architecture review of 2fc7c80 (1ee08ea..2fc7c80, dw/tasks/timeline_export.py, the only file touched) by reviewer claude-opus-5-5 via anthropic: pass, no findings. Read against docs/ARCHITECTURE.md and stage-c-guardrails.md item 3. Second owner: the new bounds check (shot start_frame + num_frames <= the video's frame_count) sits next to this module's own _refuse_overlaps. No map row assigns caller-shot-list validation to another module: the 'Shots in a joined video' row (dw/shots.py) covers AudioVideo constructors and measured sample spans, and dw/shots.py has no bounds helper. The frame count comes from the existing _facts(path) call, so the change adds no media decode. Build vs buy: it is integer comparison, so there is nothing a pyproject dependency would replace. Map: no new module or owner, so no row is needed. Context: no CLAUDE.md change. The issue has no arch-approved label, and nothing needed waiving. Left with owner:tester + status:fixed-pending-verify for the MCP verify.