heygen-com/hyperframes · error · Error
storyboard frame ${frameIndex} not found
Error message
storyboard frame ${frameIndex} not found What it means
Thrown by setFrameField() when the 1-based frameIndex does not correspond to a frame heading in the source STORYBOARD.md. frameBounds() walks the markdown splitting headings via FRAME_HEADING_RE; if `frameBounds(lines)[frameIndex - 1]` is undefined, the requested frame does not exist in the authored storyboard.
Source
Thrown at packages/core/src/storyboard/editStoryboard.ts:91
/**
* Set (or insert) a metadata field on the frame at `frameIndex` (1-based).
* Replaces an existing `- key: …` line (matching any alias) in place; otherwise
* inserts a new line right after the frame heading.
*
* Throws when the frame doesn't exist, so a stale/raced index (e.g. the frame
* was deleted on disk after render) surfaces as an error instead of a silent
* no-op the UI would report as a successful save.
*/
export function setFrameField(
source: string,
frameIndex: number,
key: string,
value: string,
opts: { aliases?: readonly string[]; quote?: boolean } = {},
): string {
const lines = source.split(/\r?\n/);
const target = frameBounds(lines)[frameIndex - 1];
if (!target) throw new Error(`storyboard frame ${frameIndex} not found`);
const aliases = new Set([key, ...(opts.aliases ?? [])].map((k) => k.toLowerCase()));
const formatted = formatValue(value, opts.quote ?? false);
for (let i = target.start + 1; i < target.end; i++) {
const match = META_LINE_RE.exec(lines[i] ?? "");
if (match && aliases.has((match[2] ?? "").toLowerCase())) {
lines[i] = `${match[1]}${match[2]}${match[3]}${formatted}`;
return lines.join("\n");
}
}
lines.splice(target.start + 1, 0, `- ${key}: ${formatted}`);
return lines.join("\n");
}
/** Set the voiceover (guide) line for a frame, matching any voiceover alias. */
export function setFrameVoiceover(source: string, frameIndex: number, value: string): string {View on GitHub (pinned to c2996c8626)
Solutions
- Before writing, re-read the current STORYBOARD.md and re-parse frame bounds to confirm the index still exists.
- Validate that `frameIndex >= 1 && frameIndex <= parsedFrameCount` before calling setFrameField.
- If the frame was deleted intentionally, discard the edit rather than retrying against a stale index.
- Refresh the editor's frame list from disk on focus/save to avoid stale-index writes.
Example fix
// before
const next = setFrameField(source, frameIndex, "status", "approved");
// after
const bounds = parseStoryboard(source); // exposes frame count
if (frameIndex < 1 || frameIndex > bounds.frames.length) {
throw new Error(`frame ${frameIndex} out of range (1..${bounds.frames.length})`);
}
const next = setFrameField(source, frameIndex, "status", "approved"); Defensive patterns
Strategy: validation
Validate before calling
import { parseStoryboard } from "./parseStoryboard.js";
function assertFrameInRange(source: string, frameIndex: number): void {
const { frames } = parseStoryboard(source);
if (!Number.isInteger(frameIndex) || frameIndex < 1 || frameIndex > frames.length) {
throw new RangeError(`frame index ${frameIndex} out of range (1..${frames.length})`);
}
} Type guard
function isValidFrameIndex(source: string, frameIndex: unknown): boolean {
if (typeof frameIndex !== "number" || !Number.isInteger(frameIndex)) return false;
const { frames } = parseStoryboard(source);
return frameIndex >= 1 && frameIndex <= frames.length;
} Try / catch
try {
return setFrameField(source, frameIndex, key, value, opts);
} catch (e) {
if (/frame .* not found/.test(String(e))) {
const { frames } = parseStoryboard(source);
throw new RangeError(`frame ${frameIndex} no longer exists (have 1..${frames.length}); refresh from disk`, { cause: e });
}
throw e;
} Prevention
- Re-read STORYBOARD.md and re-parse frame bounds immediately before writing.
- Treat the frame list as stale on focus/save; refresh from disk.
- Remember frameIndex is 1-based in setFrameField.
When it happens
Trigger: Calling setFrameField(source, frameIndex, key, value) with frameIndex greater than the number of frame headings, frameIndex <= 0, or frameIndex valid at authoring time but stale after the storyboard was edited/deleted on disk. Used by the storyboard frame-focus editor when persisting `voiceover`/`status` edits.
Common situations: UI editor holding a stale frame list after the underlying STORYBOARD.md changed in another tab/Git pull; passing an index from a 0-based caller into the 1-based API; deleting frames in the markdown while the editor session still references the old indices; concurrent edits where a collaborator removed the frame between read and write.
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/7ead3beebb82a708.
Report an issue: GitHub.