fix(renderable): KeyError in @gr.render when inner gr.Examples pops a fake-event fn#13344
Open
GopalGB wants to merge 1 commit intogradio-app:mainfrom
Open
fix(renderable): KeyError in @gr.render when inner gr.Examples pops a fake-event fn#13344GopalGB wants to merge 1 commit intogradio-app:mainfrom
GopalGB wants to merge 1 commit intogradio-app:mainfrom
Conversation
`Renderable.apply` snapshots `fns_from_last_render` before running the user render function and then walks that snapshot to drop fns whose `render_iteration` is stale. If the user render function (or anything it calls -- e.g. `gr.Examples`, which transiently appends and then pops a "fake_event" function in `gradio/helpers.py:580`) leaves `blocks_config.fns` without an entry for one of the captured ids, the cleanup loop crashed with `KeyError`. Reproduction (issue gradio-app#12081): with gr.Blocks() as demo: with gr.Tab(key="tab", label="Tab"): dropdown = gr.Dropdown(["a", "b", "c"]) @gr.render(inputs=[dropdown]) def render(value): text = gr.Textbox(key="text", label="Text") gr.Examples( examples=["a1", "a2", "a3"], example_labels=["First", "Second", "Third"], inputs=[text], ) Triggering the dropdown raised: File ".../gradio/renderable.py", line 89, in apply if blocks_config.fns[fn._id].render_iteration != self.render_iteration: KeyError: 3 The cleanup intent is "drop stale fns from the previous iteration", so an already-absent entry is in the desired state. Switching the lookup to `.get(fn._id)` and continuing on `None` removes the crash without changing the cleanup outcome. Test: `test/test_renderable.py::test_render_apply_does_not_raise_keyerror_when_fns_are_popped` deterministically reproduces the issue without spinning up an HTTP queue: it captures the renderable registered by `@gr.render`, registers a stub block-function whose `rendered_in` matches, and pops it during the inner render to model `gr.Examples`' fake-event cleanup. Without this fix the test fails with `KeyError`; with the fix it passes. Closes gradio-app#12081 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Repro
Closes #12081.
```python
import gradio as gr
with gr.Blocks() as demo:
with gr.Tab(key="tab", label="Tab"):
dropdown = gr.Dropdown(["a", "b", "c"])
demo.launch()
```
Selecting any value in the dropdown raised:
```
File ".../gradio/renderable.py", line 89, in apply
if blocks_config.fns[fn._id].render_iteration != self.render_iteration:
KeyError: 3
```
(Removing either the `gr.Tab` wrapper OR the `example_labels` argument made the crash go away — both signals point to the same code path.)
Root cause
`Renderable.apply` (`gradio/renderable.py`) snapshots `fns_from_last_render` before running the user render function (lines 68–71), then walks the snapshot to clean up stale fns:
```python
for fn in fns_from_last_render:
if blocks_config.fns[fn._id].render_iteration != self.render_iteration:
del blocks_config.fns[fn._id]
```
When the inner render contains a `gr.Examples`, `Examples` transiently appends and then pops a "fake_event" function (`gradio/helpers.py:580`) for cache-on-launch processing. After this, `blocks_config.fns` no longer has an entry for that id — but the snapshot still does, and `blocks_config.fns[fn._id]` raises `KeyError`.
Fix
The cleanup intent is "drop stale fns from the previous iteration" — an already-absent entry is in the desired state. Switching the lookup to `.get(fn._id)` and `continue`-ing on `None` removes the crash without changing the cleanup outcome:
```python
for fn in fns_from_last_render:
current_fn = blocks_config.fns.get(fn._id)
if current_fn is None:
continue
if current_fn.render_iteration != self.render_iteration:
del blocks_config.fns[fn._id]
```
Test
New file `test/test_renderable.py` adds `test_render_apply_does_not_raise_keyerror_when_fns_are_popped` which deterministically reproduces the bug without spinning up an HTTP queue:
Without the fix this test fails at `gradio/renderable.py:89` with `KeyError`; with the fix it passes.
```
$ pytest test/test_renderable.py -v
test/test_renderable.py::test_render_apply_does_not_raise_keyerror_when_fns_are_popped PASSED
```
A changeset entry is included.
🤖 Generated with Claude Code
Note
Low Risk
Low risk: a small defensive change in
Renderable.apply’s cleanup loop plus a targeted regression test; behavior only differs when a previously-rendered function id is already missing.Overview
Prevents
@gr.renderfrom crashing when an inner render (notablygr.Examples) transiently registers and then removes a block function.Renderable.applynow treats missing entries inblocks_config.fnsas already-cleaned rather than indexing and raisingKeyError, and a new regression test reproduces the “popped fake_event” scenario to ensureapply()completes without error. A patch changeset is included.Reviewed by Cursor Bugbot for commit 3f37875. Bugbot is set up for automated code reviews on this repo. Configure here.