{"record":{"id":"34551893abc0454e","repo":"anthropics/skills","slug":"no-frames-to-save-add-frames-with-add-frame-fir","errorCode":null,"errorMessage":"No frames to save. Add frames with add_frame() first.","messagePattern":"No frames to save\\. Add frames with add_frame\\(\\) first\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/slack-gif-creator/core/gif_builder.py","lineNumber":180,"sourceCode":"        output_path: str | Path,\n        num_colors: int = 128,\n        optimize_for_emoji: bool = False,\n        remove_duplicates: bool = False,\n    ) -> dict:\n        \"\"\"\n        Save frames as optimized GIF for Slack.\n\n        Args:\n            output_path: Where to save the GIF\n            num_colors: Number of colors to use (fewer = smaller file)\n            optimize_for_emoji: If True, optimize for emoji size (128x128, fewer colors)\n            remove_duplicates: If True, remove duplicate consecutive frames (opt-in)\n\n        Returns:\n            Dictionary with file info (path, size, dimensions, frame_count)\n        \"\"\"\n        if not self.frames:\n            raise ValueError(\"No frames to save. Add frames with add_frame() first.\")\n\n        output_path = Path(output_path)\n\n        # Remove duplicate frames to reduce file size\n        if remove_duplicates:\n            removed = self.deduplicate_frames(threshold=0.9995)\n            if removed > 0:\n                print(\n                    f\"  Removed {removed} nearly identical frames (preserved subtle animations)\"\n                )\n\n        # Optimize for emoji if requested\n        if optimize_for_emoji:\n            if self.width > 128 or self.height > 128:\n                print(\n                    f\"  Resizing from {self.width}x{self.height} to 128x128 for emoji\"\n                )\n                self.width = 128","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/anthropics/skills/blob/f6656c1256d5a8adfa37db9110046ef20bac644c/skills/slack-gif-creator/core/gif_builder.py#L162-L198","documentation":"GifBuilder.save() refuses to write a GIF when its internal frames list is empty, because there is nothing to encode. The ValueError tells you to call add_frame() first; it is a straightforward precondition violation, not a resource or encoding failure.","triggerScenarios":"Calling save(output_path) on a fresh GifBuilder; calling save() after a deduplicate/reset step removed all frames; a frame-generation loop whose condition never fired (e.g. zero scenes parsed from user input) so add_frame() was never reached.","commonSituations":"Building a GIF from user-supplied text/emoji where empty input yields zero frames; forgetting that add_frame() must precede save() in the builder pipeline; conditional frame loops (only add on success) where all attempts failed silently.","solutions":["Verify frames were added before saving: ensure add_frame() ran at least once","Validate upstream input (e.g. non-empty prompt/text) before starting the build","If frames can legitimately be zero, skip the build entirely instead of calling save()"],"exampleFix":"# before\ngif.save(out_path)\n# after\nif not gif.frames:\n    raise ValueError(\"nothing to render: provide text/scenes first\")\ngif.save(out_path)","handlingStrategy":"validation","validationCode":"def builder_has_frames(builder) -> bool:\n    return len(getattr(builder, \"frames\", [])) > 0","typeGuard":null,"tryCatchPattern":"try:\n    info = builder.save(out)\nexcept ValueError as e:\n    if \"No frames\" in str(e):\n        return None  # skip GIF creation for empty input\n    raise","preventionTips":["Check len(builder.frames) right after the frame-generation loop, before save()","Reject empty user input (text/scenes) at the API boundary","Log the frame count when building so zero-frame runs are visible"],"tags":["gif","precondition","validation","slack"],"backgroundTag":null,"analyzedSha":"f6656c1256d5a8adfa37db9110046ef20bac644c","analyzedAt":"2026-08-14T16:09:17.493Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}