You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
a skipped manager leaves imported subordinates with a dangling reports_to #14811
reports_to is then remapped through that map. But an agent can be skipped —
already exists, and now also an unknown or unrunnable adapter_type — while its
pre-minted id stays in the map. Any subordinate that does import gets a reports_to pointing at a row that was never created.
agent_org_nodes.reports_to is a plain indexed String with no foreign key
(migrations/versions/20260315_009_agent_org_hierarchy.py), so nothing rejects it.
Pre-existing, but wider now
The "already exists — skipped" path has had this shape all along. #14808 widens it
materially: a CLI-less host can now skip every agent declaring a given adapter,
so an entire manager subtree can vanish while its reports import and hang off
nothing.
Fix
Track which agents were actually inserted rather than which were pre-minted, and
either null a subordinate's reports_to when its manager was skipped, or add an
explicit warning naming the orphaned relationship. Nulling is probably right — an
agent reporting to nobody is a state the org chart already handles, whereas one
reporting to a phantom is not.
Acceptance criteria
A template where a manager is skipped and a subordinate is not leaves the
subordinate with a resolvable reports_to, or none
The import warns when a relationship is dropped, so it is visible
A test covers manager-skipped-subordinate-kept and fails if the remap goes
back to using pre-minted ids unconditionally
Both skip reasons are covered — already-exists and adapter-gated — since they
reach the same code by different routes
Found reviewing #14808. Not introduced there; that PR is what makes it reachable
often enough to matter.
Dev_new_gui is not the default branch, so closing keywords do not fire —
closing by hand with evidence.
Verified in base
All 16 touched files are byte-identical between the branch tip and origin/Dev_new_gui (blob hashes, not commit reachability — a squash merge
leaves the branch's own commits permanently unreachable either way). Behaviour
confirmed present in base: _link_reporting_lines defined and called, the
company-scoped UPDATE, and the two i18n keys in each locale.
What the fix is
execute_import pre-minted a destination UUID for every agent before any
INSERT, then _import_agent decided per agent whether the row was created. Pass
1 predicted the insert set and pass 2 decided it, and nothing reconciled the
two — so a subordinate whose manager was skipped was written with the manager's
pre-minted UUID, an id for a row that was never inserted. There is no FK on reports_to, so nothing rejected it, ever.
A new pass 3 attaches reports_to only for managers that actually landed, and
reports the rest as structured dropped_reporting_lines.
Why this mattered more than a broken pointer
/api/agents' org tree roots on reports_to IS NULL and matches children by agent_id, so an agent referencing a nonexistent manager is neither a root
nor anyone's child — it disappeared from the tree entirely, could not be
delegated to, and could not escalate, while the import reported success. The LLC
org chart still rendered it, because that endpoint promotes an unresolvable
parent to a root. That asymmetry is why this survived: the UI most people look
at hid it.
Mutation evidence
The linker reproduced faithfully, then mutated to resolve against the pass-1
prediction (the pre-fix behaviour), with the manager skipped:
dst-mgr is precisely the pre-minted id of a row that was never inserted.
Added in review
The UPDATE is now scoped by company_id as well as agent_id. agent_id
carries a table-wide UNIQUE constraint today so the clause is redundant — but
that constraint is declared in a migration three files away, and the natural
future change (making it unique per company, the first time two tenants want
the same agent slug) would silently turn this into a cross-tenant rewrite. The
test asserts the bound parameter, not the SQL text.
The frontend check was red twice on unrelated tests before passing — a
10s-timeout flake tracked as #14842, not a defect in this change. It was
re-run with the reasoning posted publicly rather than retried silently.
Activity
fix(llc): gate the template import with the same adapter checks as hi…
fix(llc): scope the reporting-line update to the importing company (#…
fix(llc): attach reports_to only for managers that actually landed (#…
mrveiss commentedon Aug 22, 2026
Closed by #14837, squash-merged as
0703223db.Dev_new_guiis not the default branch, so closing keywords do not fire —closing by hand with evidence.
Verified in base
All 16 touched files are byte-identical between the branch tip and
origin/Dev_new_gui(blob hashes, not commit reachability — a squash mergeleaves the branch's own commits permanently unreachable either way). Behaviour
confirmed present in base:
_link_reporting_linesdefined and called, thecompany-scoped
UPDATE, and the two i18n keys in each locale.What the fix is
execute_importpre-minted a destination UUID for every agent before anyINSERT, then
_import_agentdecided per agent whether the row was created. Pass1 predicted the insert set and pass 2 decided it, and nothing reconciled the
two — so a subordinate whose manager was skipped was written with the manager's
pre-minted UUID, an id for a row that was never inserted. There is no FK on
reports_to, so nothing rejected it, ever.A new pass 3 attaches
reports_toonly for managers that actually landed, andreports the rest as structured
dropped_reporting_lines.Why this mattered more than a broken pointer
/api/agents' org tree roots onreports_to IS NULLand matches children byagent_id, so an agent referencing a nonexistent manager is neither a rootnor anyone's child — it disappeared from the tree entirely, could not be
delegated to, and could not escalate, while the import reported success. The LLC
org chart still rendered it, because that endpoint promotes an unresolvable
parent to a root. That asymmetry is why this survived: the UI most people look
at hid it.
Mutation evidence
The linker reproduced faithfully, then mutated to resolve against the pass-1
prediction (the pre-fix behaviour), with the manager skipped:
dst-mgris precisely the pre-minted id of a row that was never inserted.Added in review
The
UPDATEis now scoped bycompany_idas well asagent_id.agent_idcarries a table-wide UNIQUE constraint today so the clause is redundant — but
that constraint is declared in a migration three files away, and the natural
future change (making it unique per company, the first time two tenants want
the same agent slug) would silently turn this into a cross-tenant rewrite. The
test asserts the bound parameter, not the SQL text.
CI
59 checks passed, 0 failed, 12/12 python-suite shards, frontend suite green.
The frontend check was red twice on unrelated tests before passing — a
10s-timeout flake tracked as #14842, not a defect in this change. It was
re-run with the reasoning posted publicly rather than retried silently.
Follow-ups filed from this work
reports_to,org_role,titleandcapabilities, so a round trip flattens the org chart. Must land after thisone, or dangling references become common.
seed_work_itemswhile the importer readswork_items.