Repository navigation
Description
Plugin Version
1.0.0 (also reproduced on main at b34ee80)
NetBox Version
4.7.4 with netbox-branching 1.2.3 (also reproduced with netbox-branching main at 5f72279)
Python Version
3.12
Steps to Reproduce
- Create branch A and activate it.
- In branch A, create a Custom Object Type
widgetwith a text fieldname. Do not merge. - In the same process, create branch B and provision it.
The process that matters is the one that runs the provisioning job. It fails as soon as it has resolved widget's dynamic model for any reason. Two ways to get there that don't need a shell:
- Branch A's merge fails (for example because main already has a type with the same name), so the rq worker that ran the merge now has the model registered. Every later provisioning job on that worker fails, even after branch A is deleted.
- A worker runs any job that touches a widget in branch A (search indexing, reindex, webhooks), then picks up a provisioning job.
Minimal shell version (one process):
with activate_branch(a), event_tracking(request):
CustomObjectType.objects.create(name='widget', slug='widget') # in branch A
Branch(name='b').save(provision=False); b.provision(user=user) # same processExpected Behavior
Branch B provisions. The type created in branch A has no table in main yet, so it should not be part of the set of tables replicated into B.
Observed Behavior
Provisioning of B fails and the branch is left in the failed state:
django.db.utils.ProgrammingError: relation "public.custom_objects_150" does not exist
LINE 1: ...BLE "branch_ba0tjlii"."custom_objects_150" ( LIKE "public"."custom_objects_150" INCLUDING GENERATED )
File "netbox_branching/models/branches.py", line 1410, in provision (netbox-branching main; 1.2.3 fails at the same CREATE TABLE … LIKE in provisioning.py)
Every further provision in that process fails the same way until the process is restarted. A fresh process provisions fine, because ContentType.model_class() returns None there and netbox-branching skips the entry; it starts failing as soon as it, too, resolves the branch's type (verified: fresh process provisions OK, then serves one object create in branch A, then its next provision fails).
Related observations from the same runs:
- A failed merge of branch A (duplicate type name) followed by deleting branch A does not clear the condition in that process.
- Deleting branch A leaves a
django_content_type/core_objecttyperow fortable150modelin main with noCustomObjectTypebehind it.
Suspected Cause
custom_object_type_post_save_handler creates the type's ObjectType in main at creation time, also when the type is created inside a branch (core_objecttype / django_content_type are not branched), and the ObjectType carries the branching feature.
netbox_branching.utilities.get_tables_to_replicate() builds the replication list from ObjectType.objects.with_feature('branching') and model_class(). Model registration in apps.all_models is process-global; once Table150Model has been built in a process for branch A, model_class() resolves it in main context as well, and its db_table is added to the list although the table only exists in branch A's schema. CREATE TABLE … LIKE public.custom_objects_150 then fails.
Proposed Fix
Any of:
- In
CustomObjectsPluginConfig.get_model()(the pathContentType.model_class()takes), returnNonewhen no active branch is set and theCustomObjectTyperow does not exist in main. That matches what a fresh process already does. - Have the Custom Objects branching resolver exclude types whose table is absent from
publicfrom the replicated set, or haveget_tables_to_replicate()checkto_regclass(main_schema.table)before adding a dynamic model's table. - Remove the
tableNmodelContentType / ObjectType row when a branch that created the type is deleted without merging (separate, smaller clean-up).
Activity
bctiemann commentedon Nov 10, 2026
Triage notes. The diagnosis above matches the code. The fix is split between netbox-branching and custom objects.
Root cause (confirmed in code):
ObjectTypeis created in main even when the type is created in a branch (models.py:3037-3046), andcreate_model()gives it thebranchingfeature (models.py:2789-2799).core_objecttypeisn't branched.CustomObjectsPluginConfig.get_model()(__init__.py:680-705) returns it in any context, main included, without checking that the type exists there.get_tables_to_replicate()adds the table (and M2M tables) of everybranchingobject type whosemodel_class()resolves, without checking the table exists. Provisioning'sCREATE TABLE … LIKE public.<table>then fails (branches.py:1410).SearchIndexondisplay_expressionfor Quick search / dynamic object selector #729 does not fix this: its existence check is only on its new loop for non-change-logged resolver models. CO's dynamic models are change-logged, so they go through the old path.Where the fix lives:
get_tables_to_replicate(), logging a warning. A stale object type then can't block provisioning, whatever plugin left it. This alone fixes the provisioning failure.get_model()shouldn't return a model in main context when main has no row for the type; a fresh process already behaves that way.ObjectType/ContentTypewhen a branch that created the type is deprovisioned without merging (netbox-branching'spost_deprovisionsignal).branchingfeature only once the type exists in main.Dependencies: netboxlabs/netbox-branching#732 (same function as #729, same release). Related: netboxlabs/netbox-branching#683.
Severity / size: high (one affected rq worker fails every later provision until it restarts, and an ordinary failed merge can trigger it). Small in netbox-branching; small to medium in CO.
Related: #1046 has the same underlying cause (a branch-only type resolved in main), and the CO
get_model()guard helps there too. #1047 touches the same object-type lifecycle. #1044 also touchescreate_model().Release plan for this batch (#1042–#1048): the branching-side fixes (netboxlabs/netbox-branching#728 / PR netboxlabs/netbox-branching#729, netboxlabs/netbox-branching#731, netboxlabs/netbox-branching#732) go into the next netbox-branching release. The custom-objects fixes land in the custom-objects release that raises the minimum netbox-branching version to it. Suggested custom-objects order: #1048, then #1042 + #1043, then #1047, then #1044 + #1045. Line numbers above are at custom-objects
b34ee80and netbox-branching5f72279.