Skip to content

CXP-768 Gate account re-parenting on org/OU sync opt-in - #139

Merged
JavierCarnelli-ConductorOne merged 5 commits into
mainfrom
fix/cxp-768
Aug 3, 2026
Merged

CXP-768 Gate account re-parenting on org/OU sync opt-in#139
JavierCarnelli-ConductorOne merged 5 commits into
mainfrom
fix/cxp-768

Conversation

@JavierCarnelli-ConductorOne

Copy link
Copy Markdown
Contributor

accountResourceType.List() re-parented every account onto its Organizations Root/OU whenever organizations:ListParents succeeded, regardless of whether the organization/organizational_unit resource types (OptInRequired, off by default) were actually being synced. In the default orgs+sso configuration this left every account with a parent pointing at a resource that was never synced, and c1 silently drops the reference as a dangling MISSING RESOURCE.

Thread cli.ConnectorOpts through New() (previously discarded) to learn whether this sync run will actually emit organization/ organizational_unit, and gate both the ListParents call and the resulting parent attachment on it -- including the resolved parent's specific type, so partial opt-in can't produce a dangling parent either.

@linear-code

linear-code Bot commented Jul 23, 2026

Copy link
Copy Markdown

CXP-768

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Connector PR Review: CXP-768 Gate account re-parenting on org/OU sync opt-in

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: loaded .claude/skills/ci-review.md from trusted base d48bf6add343
Review mode: full
View review run

Review Summary

Scanned the full PR diff for security and correctness. The change threads cli.ConnectorOpts through New() and gates account re-parenting (the ListParents call and parent attachment, including the resolved parent's specific type) on whether this sync run will actually emit the OptInRequired organization/organizational_unit types. The gate is correct: WillSyncResourceType uses the identical condition (empty filter OR type-in-filter) that the SDK syncer applies when actually syncing a resource type (syncer.go:1121-1133), so willSyncOrganization precisely tracks whether org will be synced — no dangling MISSING RESOURCE parent in either the default-all or explicit-filter path. The prior review's gap (organization opted out but organizational_unit opted in) is now covered by TestAccountList_SkipsReparentWhenOnlyOrganizationalUnitSynced. No new blocking issues found.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/connector/account.go:238 — the missing-organizations:ListParents diagnostic was degraded Warn to Debug; since it now only fires when org hierarchy was opted in but the permission is denied, Warn (repo criteria L1) would keep the one actionable signal visible. Low confidence; visibility tradeoff, not correctness.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In pkg/connector/account.go:
- Around line 238: The "missing organizations:ListParents permission; accounts synced
  flat" message was changed from l.Warn to l.Debug. Because re-parenting is now gated on
  hierarchySync.Organization, this branch only executes when the customer explicitly opted
  into org hierarchy but lacks the ListParents permission - the exact situation where the
  actionable "Add organizations:ListParents" guidance is most useful. Consider restoring
  l.Warn (repo review criteria L1 classifies a 403/permission-denied as Warn) so the
  diagnostic stays visible. This is a visibility judgment call, not a correctness bug.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/connector/account.go Outdated
@Bencheng21

Copy link
Copy Markdown

Adjacent edge not addressed: the OU→Root parent edge (organizationalUnitResource → WithParentResourceID) has the same dangling shape if organizational_unit is synced while organization (Root) isn't. In practice the OU crawl is seeded from the Root, so "OU without org" is likely unreachable — but if that invariant changes, the same MISSING RESOURCE could reappear one tier up. Note on the ticket.

Could you confirm with OU without org is unreachable?

@JavierCarnelli-ConductorOne

Copy link
Copy Markdown
Contributor Author

Hey @Bencheng21 ! About your question:

Could you confirm with OU without org is unreachable?

I can confirm. "OU without org" is unreachable. As you mentioned, this is covered by design. If the Root Org is not synced, no OU will be on the result set.
This is likely not going to change. So we should be good with it

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Comment thread pkg/connector/connector.go
Comment thread pkg/connector/account.go Outdated
Comment thread pkg/connector/account.go Outdated
Comment thread pkg/connector/account.go Outdated

@luisina-santos luisina-santos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the fix is correct and well-tested. Two follow-ups worth addressing before merge, neither changing the approach.

Resolving the [Major] on connector.go:257@sergiocorral-conductorone's reading of the SDK is accurate: in baton-sdk@v0.18.4, WillSyncResourceType (pkg/cli/cli.go:89) returns true whenever the filter is empty and never consults OptInRequired. Grepping the whole SDK, the only non-test references to OptInRequired are that doc comment and connectorbuilder.go:426, where it's just reported in capabilities metadata — the syncer never enforces it.

That said, the concern doesn't apply: C1 always sends an explicit SyncResourceTypeIDs list when it triggers a sync, so SyncFilterIsExplicit() is true and the gate does a real set lookup on a production path.

And the true default is positively correct, not merely harmless. In an unfiltered run (local CLI), the syncer syncs everything the connector advertises — org and OU included — so the resolved parent does get emitted and is not dangling. Defaulting to "will sync" is precisely right there. Gating on SyncFilterIsExplicit() && WillSyncResourceType(id) would make that case worse: it would suppress re-parenting on a run that actually does sync the hierarchy. Current code is right as-is.

Please address — [Minor], account.go:209 (raised by @sergiocorral-conductorone, seconded by @FeliLucero1). Both willSyncParentType branches already require o.willSyncOrganization, so the outer || guard is wider than the inner condition can ever use: in (org=false, OU=true) it fires a rate-limited ListParents per account that can never produce an attachment. Collapsing the outer guard to if o.willSyncOrganization removes the wasted call and makes the org→OU dependency structural rather than comment-only. @FeliLucero1's block above is the change verbatim.

While in there: the new tests cover (false,false) and (true,false), but (false,true) is the one combination not covered — and it's the case this guard change is about. Worth adding, asserting listParentsCalls == 0.

Non-blocking — [Nit], account.go:982 (@FeliLucero1). Agreed and confirmed: accountBuilder is 8 positional params with two adjacent untyped bools, and neither newOrgAccountWithSyncFilter nor the behavior-test helper exercises the real AWS{}ResourceSyncers() wiring, so a transposition at that one live call site would be a silent behavior change rather than a compile error. Fine to defer, but if the tests are being touched for the (false,true) case anyway, a small named struct for the two flags is cheap insurance.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@FeliLucero1 FeliLucero1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the refactor holds up. Verified the guard simplification is behavior-equivalent for the 3 (Organization, OrganizationalUnit) combos where it matters (attach outcome unchanged), and traced through the OU-crawl seeding logic + the SDK's WillSyncResourceType to confirm the org→OU dependency claim in the new comment is accurate. Left 2 small non-blocking notes inline — one's a genuinely new observation building on the test-coverage gap already open on this thread, the other's just a stale comment. Neither should hold up merge.

Comment thread pkg/connector/account.go
Comment thread pkg/connector/account.go Outdated
Comment thread pkg/connector/connector.go
Comment thread pkg/connector/organization_test.go
accountResourceType.List() re-parented every account onto its
Organizations Root/OU whenever organizations:ListParents succeeded,
regardless of whether the organization/organizational_unit resource
types (OptInRequired, off by default) were actually being synced.
In the default orgs+sso configuration this left every account with a
parent pointing at a resource that was never synced, and c1 silently
drops the reference as a dangling MISSING RESOURCE.

Thread cli.ConnectorOpts through New() (previously discarded) to
learn whether this sync run will actually emit organization/
organizational_unit, and gate both the ListParents call and the
resulting parent attachment on it -- including the resolved parent's
specific type, so partial opt-in can't produce a dangling parent
either.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread pkg/connector/account.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne merged commit 2e6735e into main Aug 3, 2026
9 checks passed
@JavierCarnelli-ConductorOne
JavierCarnelli-ConductorOne deleted the fix/cxp-768 branch August 3, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants