Skip to content

feat(init): list sub-orgs with counts in org picker (ENG-4673)#157

Open
IgorHorta wants to merge 2 commits intomainfrom
igor/eng-4673-cli-init-should-list-suborgs
Open

feat(init): list sub-orgs with counts in org picker (ENG-4673)#157
IgorHorta wants to merge 2 commits intomainfrom
igor/eng-4673-cli-init-should-list-suborgs

Conversation

@IgorHorta
Copy link

Summary

  • Adds a call to GET /v1/organization/accessible-with-sub-orgs to enrich the org picker with sub-org counts (e.g. Acme Corp (3 sub-orgs)). The existing flat GET /v1/organization list remains the source of truth — if the new endpoint is unavailable (older self-hosted instances return 404), the picker falls back gracefully with no visible change to the user.
  • When an org has sub-orgs, a second prompt appears to let the user pick the root org or a specific sub-org. Selecting a sub-org scopes the JWT to that sub-org, which then filters the project list correctly.
  • The same pickOrganization helper is reused in the login flow with a context-appropriate label.
  • Minor quality fixes from code review: named api.Organization type (replaces anonymous struct), debug log on sub-org fetch failure, comment explaining the 404 sentinel check ordering.

Test plan

  • infisical init on an account with no sub-orgs — single prompt, behaviour unchanged
  • infisical init on an account with sub-orgs — first prompt shows counts, second prompt shows root org + sub-orgs
  • infisical init against an older self-hosted instance (404 on new endpoint) — falls back to flat list, no error shown
  • infisical login interactive flow shows correct "log into" label instead of "select a project from"
  • E2E tests pass (test/login_test.go)

🤖 Generated with Claude Code

@linear
Copy link

linear bot commented Mar 23, 2026

- Add GET /v1/organization/accessible-with-sub-orgs API call with
  ErrEndpointNotSupported sentinel for older self-hosted instances
- Use flat GET /v1/organization list as source of truth; enrich with
  sub-org counts best-effort (failures fall back silently with debug log)
- Show two-level picker: root org with sub-org count, then sub-org selector
- Extract api.Organization named type; apply same sub-org picker to login flow
- Update E2E login test to handle optional sub-org prompt step
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR enriches the infisical init and infisical login org-picker with sub-org counts and a two-level selection prompt, while falling back gracefully on older self-hosted instances that return 404 for the new endpoint. The core logic — extracting pickOrganization, adding typed models, and the 404 sentinel — is well-structured and safe.

Key changes:

  • New CallGetAllOrganizationsWithSubOrgs API call with a proper 404-before-IsError() sentinel guard for backward compatibility
  • pickOrganization helper consolidates org selection for both init and login flows
  • BuildOrgRootLabels / BuildSubOrgPickerItems utilities replace the old GetOrganizationsNameList
  • Test updated for UserInitCmd to handle the new sub-org step

Issues found:

  • UserLoginCmd test not updated: Since login --interactive now also calls pickOrganization, the interactive login test will hang when run against an account that has sub-orgs — the "Which scope within" prompt is unhandled
  • OrgPickerItem.Label is populated but never read by callers; the parallel labels slice is used instead, creating a subtle redundancy
  • No documentation exists in the repository, so customers may not discover the sub-org scoping feature unless it is documented externally

Confidence Score: 4/5

  • Safe to merge for the primary init flow; the login interactive test gap should be fixed before this is validated in CI against accounts with sub-orgs.
  • The core feature logic is sound and the fallback strategy is correct. The only concrete bug is the missing sub-org step handler in UserLoginCmd, which would cause the login E2E test to stall for accounts with sub-orgs. All other findings are style/cleanup items that don't affect runtime correctness.
  • test/login_test.go — UserLoginCmd needs the same sub-org step handling added to UserInitCmd

Important Files Changed

Filename Overview
packages/api/api.go Adds CallGetAllOrganizationsWithSubOrgs with correct 404-before-generic-error sentinel handling and a new ErrEndpointNotSupported error. Clean and consistent with existing patterns.
packages/api/model.go Extracts anonymous org struct to named Organization type and adds SubOrganization, OrganizationWithSubOrgs, and GetOrganizationsWithSubOrgsResponse. Clean model refactor.
packages/cmd/init.go Refactors org selection into the reusable pickOrganization helper with two-level prompts. Minor: debug log missing username per logging convention.
packages/cmd/login.go Simplifies GetJwtTokenWithOrganizationId by reusing pickOrganization. The login interactive test (UserLoginCmd) was not updated to handle the new sub-org step, which may cause test hangs against accounts with sub-orgs.
packages/util/init.go Replaces GetOrganizationsNameList with BuildOrgRootLabels and BuildSubOrgPickerItems. Logic is correct; minor: OrgPickerItem.Label is populated but never read at the call site.
test/login_test.go UserInitCmd correctly updated for the new sub-org step, but UserLoginCmd was not — the login test will stall for accounts with sub-orgs because the new "Which scope within" prompt is unhandled.

Comments Outside Diff (1)

  1. test/login_test.go, line 78-107 (link)

    P2 Login test not updated for new sub-org picker

    UserInitCmd was updated to handle the optional "Which scope within..." prompt (step 1), but UserLoginCmd was not. Since GetJwtTokenWithOrganizationId now calls pickOrganization, the interactive login flow will also show the sub-org picker when the authenticated account has sub-orgs. Without a handler for this prompt, the test will stall waiting for user input and eventually time out, causing all downstream assertions to fail.

    } else if strings.Contains(terminalOut, "Which scope within") && step < 3 {
        step += 1
        stepChan <- step
    } else if strings.Contains(terminalOut, "Enter passphrase") && step < 4 {
        step += 1
        stepChan <- step
    }

    And in the switch:

    case 3:
        ptmx.Write([]byte("\n"))
    case 4:
        // existing passphrase handling

    The existing "Infisical organization" check is the correct trigger for step 3, but you need to insert the sub-org step before the "Enter passphrase" case and renumber accordingly.

Reviews (1): Last reviewed commit: "feat(init): list sub-orgs with counts in..." | Re-trigger Greptile

@IgorHorta IgorHorta force-pushed the igor/eng-4673-cli-init-should-list-suborgs branch from 45478d7 to 396ee58 Compare March 23, 2026 20:04
- Remove unused Label field from OrgPickerItem (only ID is used by callers)
- Add username to debug log for sub-org fetch failures per logging convention
@IgorHorta
Copy link
Author

docs for the subor support as organization-id Infisical/infisical#5793

Copy link
Member

@akhilmhdh akhilmhdh left a comment

Choose a reason for hiding this comment

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

Application testing pending


// 404 means this Infisical instance doesn't support the endpoint yet (older self-hosted).
// Check before the generic IsError() so we can return the sentinel instead of an API error.
if response.StatusCode() == http.StatusNotFound {
Copy link
Member

Choose a reason for hiding this comment

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

This could be also when a resource not found as well right? I think Route not found has an explicit message

PrintErrorMessageAndExit(message)
// BuildOrgRootLabels returns first-prompt labels: org name with sub-org count when present.
// orgs is the flat list from GET /v1/organization; subOrgMap is keyed by org ID.
func BuildOrgRootLabels(orgs []api.Organization, subOrgMap map[string][]api.SubOrganization) []string {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: With go static type - we already know it's a map - i don't think we need to say it again as subOrgMap

stepChan <- step
} else if strings.Contains(terminalOut, "Which of your Infisical projects would you like to connect this project to?") && step < 1 {
step += 1;
} else if strings.Contains(terminalOut, "Which scope within") && step < 1 {
Copy link
Member

Choose a reason for hiding this comment

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

What is the meaning of scope?

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.

2 participants