Skip to content

Deleting a type in a branch leaves its reverse accessor on the target type, which then reads main's rows #1048

Open

Description

@jnovinger

Plugin Version

main at b34ee80 (introduced by #1035, v1.0.0 is not affected)

NetBox Version

4.7.4

Python Version

3.12

Steps to Reproduce

With netbox-branching 1.2.3 installed:

  1. In main, create Custom Object Type product with a text field name.
  2. Create Custom Object Type service with a text field name and an object field product pointing at product, with related name services.
  3. Create product prod-1 and service svc-1 linked to it.
  4. Create a branch and activate it.
  5. Load both types' models in the branch (any page or query that uses them).
  6. In the branch, delete the service type.
  7. In the branch, run product.services.all() on prod-1, for example from a custom script or an export template.

Expected Behavior

product has no services accessor in the branch, the same as at v1.0.0 (AttributeError).

Observed Behavior

The accessor is still there and prod-1.services.all() returns ['svc-1'], read from main's table, though service no longer exists in the branch. No error is raised.

If the branch has also changed service's schema (for example added a field tier), the same call fails instead:

ProgrammingError: column custom_objects_N.tier does not exist

Suspected Cause

#1035 installs a branch's reverse accessors on the branch's own class for the target type, and _update_branch_reverse_accessors() retracts them when the field is deleted or renamed. Deleting the whole source type takes another path. CustomObjectType.delete() calls unregister_model(model, branch_id=...) (models.py#L2982), and unregister_model() retracts accessors only when branch_id is None (models.py#L3010-L3013). The branch's entry in _branch_reverse_accessor_cache is never consulted.

Proposed Fix

No response

Activity

  1. added theissue type on Nov 10, 2026
  2. bctiemann commented on Nov 10, 2026

    @bctiemann
    Contributor

    Triage notes. Regression from #1035 (57dd567); v1.0.0 isn't affected.

    Root cause (confirmed in code):

    • CustomObjectType.delete() (models.py:2885) removes the type's fields by DB cascade, so field.delete() never runs and the cleanup in _update_branch_reverse_accessors (models.py:2665) never fires. It only runs on model regeneration (models.py:2541).
    • delete() ends with unregister_model(model, branch_id=<branch>) (models.py:2982). unregister_model (models.py:2995-3013) retracts accessors only when branch_id is None, and never reads or pops _branch_reverse_accessor_cache[(cot_id, branch_id)] (models.py:1724).
    • So the target type's branch class (still in _model_cache; nothing bumps its timestamp) keeps the services descriptor pointing at the unregistered branch class. Its branch table is gone, so the query falls through search_path to main's table: main's rows, or a ProgrammingError for a branch-only column. The fall-through is inferred from _table_exists, not traced.

    Where the fix lives: custom objects. Reverse accessors and the branch accessor cache are CO model-cache internals; netbox-branching has nothing comparable. In unregister_model's branch path, retract _branch_reverse_accessor_cache.pop((cot_id, branch_id), set()) | _reverse_accessors(model) with _retract_reverse_accessors(cot_id, accessors, branch_id=branch_id), as the main path does, and add a test from the repro above.

    • Remaining gap (inferred): deleting in a branch doesn't notify other workers (signal_custom_object_type_deleted runs only in main), so they keep the stale accessor until the target is regenerated. Decide whether that's in scope.

    Dependencies: none on netbox-branching.

    Severity / size: medium (silently reads main's data inside a branch through a reverse lookup), but it's an unreleased regression on main, so it should be fixed before the next release. Small.

    Related: #1047 is the mirror case (type deleted in main) in the same delete(); fix together if convenient.


    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 b34ee80 and netbox-branching 5f72279.

  3. added
    severity: mediumResults in substantial degraded or broken functionality for specific workflows
    on Nov 10, 2026
  4. added this to the v1.0.1 milestone on Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions