{"record":{"id":"487f85b0dfbaa2f2","repo":"BerriAI/litellm","slug":"max-recursion-depth-max-depth-reached-while-read","errorCode":null,"errorMessage":"Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit.","messagePattern":"Max recursion depth (.+?) reached while reading image bytes for Black Forest Labs image edit\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/black_forest_labs/image_edit/transformation.py","lineNumber":198,"sourceCode":"    ) -> str:\n        \"\"\"\n        Get the complete URL for the Black Forest Labs API request.\n        \"\"\"\n        base_url: str = api_base or get_secret_str(\"BFL_API_BASE\") or DEFAULT_API_BASE\n        base_url = base_url.rstrip(\"/\")\n\n        endpoint: Final = self._get_model_endpoint(model)\n        return f\"{base_url}{endpoint}\"\n\n    def _read_image_bytes(\n        self,\n        image: Any,\n        depth: int = 0,\n        max_depth: int = DEFAULT_MAX_RECURSE_DEPTH,\n    ) -> bytes:\n        \"\"\"Read image bytes from various input types.\"\"\"\n        if depth > max_depth:\n            raise ValueError(\n                f\"Max recursion depth {max_depth} reached while reading image bytes for Black Forest Labs image edit.\"\n            )\n        if isinstance(image, bytes):\n            return image\n        elif isinstance(image, list):\n            # If it's a list, take the first image\n            return self._read_image_bytes(image[0], depth=depth + 1, max_depth=max_depth)\n        elif isinstance(image, str):\n            if image.startswith((\"http://\", \"https://\")):\n                response: Final = safe_get(litellm.module_level_client, image, timeout=60.0)\n                response.raise_for_status()\n                return response.content\n            else:\n                raise ValueError(\n                    \"Unsupported image input: plain string values that are not URLs are not accepted. \"\n                    \"Provide image bytes or a file-like object.\"\n                )\n        elif hasattr(image, \"read\"):","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/black_forest_labs/image_edit/transformation.py#L180-L216","documentation":"LiteLLM's Black Forest Labs (BFL) image-edit transformer normalizes whatever you pass as `image` into raw bytes via a recursive helper `_read_image_bytes`. When the input is a list it recurses into the first element, and a depth counter (max_depth, default litellm DEFAULT_MAX_RECURSE_DEPTH = 100, overridable via the DEFAULT_MAX_RECURSE_DEPTH env var) guards against pathological nesting. This ValueError means the image argument was a list (or nested structure) nested more than 100 levels deep, so the library refuses to keep unwrapping it.","triggerScenarios":"Calling the BFL image-edit route with an image argument that is a list nested >100 levels, e.g. iteratively wrapping payloads in code like `img = [img]` inside a retry/loop, or passing a deeply nested OpenAI-style content array `[[[[...[[\"https://...\"] ...]]]]]` produced by a generic multimodal-payload builder.","commonSituations":"Frameworks that accumulate request history (chat-style message lists) and forward the whole structure as `image`; a bug where a wrapper prepends `[...]` on every attempt; feeding an OpenAI images-API-style array-of-partials that is itself nested by an upstream serialisation layer. Almost always a client-side payload-construction bug, not a BFL API issue.","solutions":["Log `type(image)` and its nesting depth before calling LiteLLM; anything beyond 1-2 list levels is a bug in your payload builder.","Unwrap the image yourself and pass the innermost element directly: bytes, a file-like object, or an http(s) URL string.","If a generic payload builder keeps re-wrapping in `[...]`, fix that loop/wrapper so it stores the image once instead of accumulating wrappers.","As a last resort only, raise the guard by setting the env var DEFAULT_MAX_RECURSE_DEPTH above your real nesting depth (default 100) - but deep nesting is almost never legitimate."],"exampleFix":"# before\nimage = [image]  # inside a retry loop -> img = [img]  # inside a retry loop -> img = [img]  # inside a retry loop -> nested 100+ deep\nresp = litellm.image_edit(model=\"flux-kontext-pro\", image=image, prompt=\"...\")\n\n# after\nresp = litellm.image_edit(model=\"flux-kontext-pro\", image=image, prompt=\"...\")","handlingStrategy":"validation","validationCode":"def normalize_bfl_image(image, max_list_depth: int = 3):\n    depth = 0\n    while isinstance(image, list):\n        if not image:\n            raise ValueError(\"empty image list\")\n        image = image[0]\n        depth += 1\n        if depth > max_list_depth:\n            raise ValueError(f\"image nested {depth} list-levels deep - fix payload builder\")\n    return image\n\nimage = normalize_bfl_image(image)  # before litellm.image_edit(...)","typeGuard":"def is_acceptable_image(x) -> bool:\n    if isinstance(x, (bytes, bytearray)):\n        return True\n    if isinstance(x, str):\n        return x.startswith((\"http://\", \"https://\"))\n    return hasattr(x, \"read\") and callable(x.read)","tryCatchPattern":null,"preventionTips":["Never wrap the image in fresh lists inside loops or retries; store the original reference.","Assert the image argument is bytes/file-like/http(s)-URL right before the API call.","Keep multimodal payload builders from forwarding whole chat-style nested arrays as `image`."],"tags":["litellm","black-forest-labs","flux","image-edit","recursion","input-validation"],"backgroundTag":"max-recursion-depth","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}