Describe your context
Describe the bug
When two (or more) callbacks legitimately write the same Output with allow_duplicate=True and prevent_initial_call=True set correctly on every writer (per the Dash duplicate-outputs docs), the browser can still raise:
Duplicate callback outputs
In the callback for output(s):
some-id.is_open
some-id.data
some-id.children@<hash>
Output 2 (some-id.children@<hash>) is already in use.
To resolve this, set `allow_duplicate=True` on
duplicate outputs, or combine the outputs into
one callback function, distinguishing the trigger
by using `dash.callback_context` if necessary.
The message is actively misleading in this situation: it tells you to set allow_duplicate=True and mentions nothing else diagnosable, but every writer of the output already has allow_duplicate=True and prevent_initial_call=True. There is no indication of why the check still failed, and no information identifying which other callback (function name / file / registration) the conflict is actually with.
Root cause (found by reading source)
create_callback_id() in dash/_utils.py builds the frontend-facing dedup key for an allow_duplicate=True output as:
def _concat(x):
nonlocal hashed_inputs
_id = x.component_id_str().replace(".", "\.") + "." + x.component_property
if x.allow_duplicate:
if not hashed_inputs:
hashed_inputs = _hash_inputs() # sha256(".".join(str(x) for x in inputs))
_id += f"@{hashed_inputs}"
return _id
i.e. the @<hash> suffix that makes two allow_duplicate writers of the same output distinguishable is derived only from that callback's Input list (confirmed: State is excluded — register_callback() passes flat_inputs and flat_state to insert_callback()/create_callback_id() as separate args, and only flat_inputs feeds the hash).
The browser-side check that raises the error (dash-renderer's findDuplicateOutputs, in dash/dash-renderer/build/dash_renderer.dev.js) then just does a outStrs[idProp] lookup against this literal id.property@hash string — it has no way to explain why two registrations produced the same hash, nor which callback the other one is.
This means the "already in use" collision can happen in at least two scenarios that have nothing to do with the two flags the error message points at:
- Coincidental Input-hash reuse — two different callbacks intentionally sharing the same
Output (the documented use case for allow_duplicate) happen to also be triggered by the exact same Input list, producing identical hashes even though the outputs/behavior are otherwise unrelated.
- Accidental double-registration — the same callback module gets imported/executed twice in one process (dev-server hot reload, duplicate import path, etc.), which trivially produces an identical Input hash against itself. This is arguably a "real" duplicate, but the error gives no way to tell it apart from case 1, or from a genuine third-callback conflict.
Neither of these is discoverable from the error text or the allow_duplicate docs, which describe allow_duplicate=True + prevent_initial_call=True as sufficient. We only found the actual mechanism by reading dash/_utils.py and the renderer bundle directly.
Expected behavior
At minimum, one of:
- The error message should say why the collision key matched (e.g. "this output's dedup key also depends on your callback's Input list — two callbacks with the same Inputs writing this Output will collide even with allow_duplicate=True") instead of only repeating the two flags that are already presumably set.
- The error should identify the other callback involved in the collision (function name, output list, or at least its full dependency signature) so the conflict is actually locatable in a large app.
- The
allow_duplicate documentation should mention that the dedup mechanism is Input-derived, since that's a real constraint on when two duplicate-writers can safely coexist.
Reproducible example
We don't have a minimal isolated repro of scenario (1) above handy (our real occurrence was a 3000+ line callback module), but the mechanism itself is directly reproducible by unit-testing dash._utils.create_callback_id with two Output objects (allow_duplicate=True) plus two different inputs lists that happen to stringify identically, or two calls with the same inputs list — both produce identical @hash suffixes. Happy to put together a full minimal Dash app repro if useful — wanted to file this first since the underlying mechanism was straightforward to pin down from source.
Workaround
Merging the conflicting callbacks into a single callback function and dispatching on dash.callback_context.triggered_id (as the error text itself suggests) avoids the issue entirely, since there's then only one registration of the output.
Describe your context
Describe the bug
When two (or more) callbacks legitimately write the same
Outputwithallow_duplicate=Trueandprevent_initial_call=Trueset correctly on every writer (per the Dash duplicate-outputs docs), the browser can still raise:The message is actively misleading in this situation: it tells you to set
allow_duplicate=Trueand mentions nothing else diagnosable, but every writer of the output already hasallow_duplicate=Trueandprevent_initial_call=True. There is no indication of why the check still failed, and no information identifying which other callback (function name / file / registration) the conflict is actually with.Root cause (found by reading source)
create_callback_id()indash/_utils.pybuilds the frontend-facing dedup key for anallow_duplicate=Trueoutput as:i.e. the
@<hash>suffix that makes twoallow_duplicatewriters of the same output distinguishable is derived only from that callback'sInputlist (confirmed:Stateis excluded —register_callback()passesflat_inputsandflat_statetoinsert_callback()/create_callback_id()as separate args, and onlyflat_inputsfeeds the hash).The browser-side check that raises the error (
dash-renderer'sfindDuplicateOutputs, indash/dash-renderer/build/dash_renderer.dev.js) then just does aoutStrs[idProp]lookup against this literalid.property@hashstring — it has no way to explain why two registrations produced the same hash, nor which callback the other one is.This means the "already in use" collision can happen in at least two scenarios that have nothing to do with the two flags the error message points at:
Output(the documented use case forallow_duplicate) happen to also be triggered by the exact sameInputlist, producing identical hashes even though the outputs/behavior are otherwise unrelated.Neither of these is discoverable from the error text or the allow_duplicate docs, which describe
allow_duplicate=True+prevent_initial_call=Trueas sufficient. We only found the actual mechanism by readingdash/_utils.pyand the renderer bundle directly.Expected behavior
At minimum, one of:
allow_duplicatedocumentation should mention that the dedup mechanism is Input-derived, since that's a real constraint on when two duplicate-writers can safely coexist.Reproducible example
We don't have a minimal isolated repro of scenario (1) above handy (our real occurrence was a 3000+ line callback module), but the mechanism itself is directly reproducible by unit-testing
dash._utils.create_callback_idwith twoOutputobjects (allow_duplicate=True) plus two differentinputslists that happen to stringify identically, or two calls with the sameinputslist — both produce identical@hashsuffixes. Happy to put together a full minimal Dash app repro if useful — wanted to file this first since the underlying mechanism was straightforward to pin down from source.Workaround
Merging the conflicting callbacks into a single callback function and dispatching on
dash.callback_context.triggered_id(as the error text itself suggests) avoids the issue entirely, since there's then only one registration of the output.