Skip to content

Reduce org ID friction: add chunk org list and auto-detect org ID in init - #462

Merged
danmux merged 1 commit into
mainfrom
org-id-friction
Aug 17, 2026
Merged

Reduce org ID friction: add chunk org list and auto-detect org ID in init#462
danmux merged 1 commit into
mainfrom
org-id-friction

Conversation

@schurchleycci

@schurchleycci schurchleycci commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Sidecar commands need a CircleCI org ID, but there was no way to discover or capture it through the CLI — users either knew it already or got blocked. This PR closes that gap.

chunk init now auto-detects the org ID after writing the project config. Single-org users get it picked silently; multi-org users see a picker. The ID is saved to .chunk/config.json so sidecar commands just work from that point on.

For cases where init isn't an option, chunk org list provides a discovery command. The orgPicker error message now points to it, and the sidecar skill uses it instead of stopping to ask the user.

Test plan

  • chunk init while authenticated to one org — org ID auto-selected and written to .chunk/config.json
  • chunk init while authenticated to multiple orgs — picker appears, selected ID written
  • chunk init while not authenticated — completes without error, no org ID written
  • chunk init in a non-interactive environment (no TTY) — completes without error or spurious warning
  • chunk init --skip-org-id — org ID detection step skipped
  • chunk org list — orgs printed in table form; --json returns JSON
  • chunk sidecar create with single-org account and no orgID in config — org auto-selected, no picker

🤖 Generated with Claude Code

@schurchleycci schurchleycci changed the title Detect org ID during chunk init Reduce org ID friction: add chunk org list and auto-detect org ID in init Jul 29, 2026

@hanabel1 hanabel1 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.

i think this could use some automated tests tho for (detectOrgID and orgPicker)

@schurchleycci

Copy link
Copy Markdown
Contributor Author

i think this could use some automated tests tho for (detectOrgID and orgPicker)

Good call, I'll add these before merging

Comment thread internal/cmd/init.go
// interactive with multiple orgs. Skipped gracefully on auth failure, no TTY,
// or cancellation — none of these are fatal for chunk init.
func detectOrgID(ctx context.Context, rc config.ResolvedConfig, streams iostream.Streams, cfg *config.ProjectConfig) {
client, err := authprompt.ResolveCircleCIClient(rc, nil)

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.

ResolveCircleCIClient returns ErrNeedsAuth for missing creds, but a genuine failure out of circleci.NewClient (bad base URL, etc.) gets swallowed here too. Gate the silent return on errors.Is(err, authprompt.ErrNeedsAuth) and warn otherwise?

Comment thread internal/cmd/init.go
}
}

// Step 3: CircleCI org ID

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.

Step numbering collides — there's already a // Step 3: Write hook config files further down.

Comment thread internal/cmd/init.go Outdated

// Step 3: CircleCI org ID
if !skipOrgID && cfg.OrgID == "" {
rc, err := config.Resolve("", "", insecureStorage)

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.

Step 2 does rc, _ := config.Resolve(...) and ignores the error; the identical failure here prints a warning. Resolve once above both steps and handle the error consistently?

Comment thread internal/cmd/org.go
var jsonOut bool

cmd := &cobra.Command{
Use: "list",

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 coverage for org list — table output, --json, and the empty case are all untested. org_test.go only exercises create.

Comment thread internal/cmd/org.go Outdated

client, err := ensureCircleCIClient(cmd.Context(), cmd, rc, io, tui.PromptHidden)
if err != nil {
return &userError{

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.

This throws away the userError ensureCircleCIClient already built (code auth.circleci_token_required, ExitAuthError) and relabels oauth/network failures as auth failures. Returning err unwrapped keeps the exit code — matters more now the skill branches on "error means not authenticated".

Comment thread internal/cmd/org.go Outdated
}

if jsonOut {
enc := json.NewEncoder(io.Out)

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.

Every other JSON path goes through iostream.PrintJSON (sidecar.go:180, config.go:72) — any reason to hand-roll the encoder here? Also a nil collabs encodes as null rather than [], which is awkward for anything parsing it.

}

func TestResolveOrgID_ConfigOrgID(t *testing.T) {
t.Setenv(config.EnvCircleCIOrgID, "env-org")

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.

Name says ConfigOrgID but this sets the env var — same path as TestResolveOrgID_FallsBackToPickOrg's sibling. The .chunk/config.json branch of config.ResolveOrgID is still uncovered.

Comment thread skills/chunk-sidecar/SKILL.md Outdated
3. If **both** are unset, run `chunk org list` to discover available orgs:
- **Single org returned** — use that ID automatically: `chunk config set orgID <id>`, then continue to Step 2.
- **Multiple orgs returned** — show the list to the user and ask which org to use **exactly once**. After they reply, run `chunk config set orgID <id>`, then continue to Step 2.
- **Error or empty list** — the user is not authenticated or has no orgs. Stop and ask them to run `chunk auth set circleci` first.

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.

Empty list exits 0 with the warning on stderr, so the agent sees success with no rows rather than an error. Point this step at chunk org list --json so there's something deterministic to parse?

Comment thread internal/cmd/init.go
if err != nil {
return
}
orgID, err := orgPicker(ctx, client)()

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.

Why go via the collaborations picker at all here? Step 1 has already detected the VCS org/repo from the git remote, and GET /api/v3/orgs?filter[slug]=<provider>/<org> resolves a slug straight to the org UUID (public-api-service v3/api.go:118). circleci-cli does exactly this in internal/apiclient/org.go:54 (ResolveOrgID), as does circleci/mcp (client/pas/org.go GetOrgBySlug).

That would be deterministic rather than a guess: no picker, no TTY dependency, correct for multi-org users, and it picks the org that actually owns this repo instead of the first collaboration in the list. The empty-list case maps to "not on CircleCI" and can stay a soft skip.

internal/circleci/projects.go:59 GetProjectBySlug also already returns org_id and is currently unused — either route gets there without a picker.

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.

On the plumbing cost: v3 responses are JSON:API-enveloped ({"data": [...], "page": {...}}), and chunk-cli's only v3 usage today is the sidecar surface, which isn't. circleci-cli already has the generic for it — internal/apiclient/client.go:154:

type v3List[T any] struct {
	Data []T `json:"data"`
	Page struct {
		Next *string `json:"next"`
		Prev *string `json:"prev"`
	} `json:"page"`
}

plus v3Entity[T] for single-resource responses (client.go:150), and filterParam(key, val)filter[key]=val (client.go:179). ResolveOrgID is then three lines on top of that.

Worth copying the same two generics + filterParam into internal/circleci rather than hand-unwrapping data at the call site — every future v3 endpoint needs them, and internal/httpcl already has QueryParam (request.go:66) so there's nothing else to build.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Why go via the collaborations picker at all here? Step 1 has already detected the VCS org/repo from the git remote, and GET /api/v3/orgs?filter[slug]=<provider>/<org> resolves a slug straight to the org UUID (public-api-service v3/api.go:118). circleci-cli does exactly this in internal/apiclient/org.go:54 (ResolveOrgID), as does circleci/mcp (client/pas/org.go GetOrgBySlug).

That would be deterministic rather than a guess: no picker, no TTY dependency, correct for multi-org users, and it picks the org that actually owns this repo instead of the first collaboration in the list. The empty-list case maps to "not on CircleCI" and can stay a soft skip.

internal/circleci/projects.go:59 GetProjectBySlug also already returns org_id and is currently unused — either route gets there without a picker.

Does that work for standalone orgs or just classic ones?

jesseworld22 added a commit that referenced this pull request Aug 13, 2026
The getting-started guide walked new users through build-prompt and code
review as its numbered path, with sidecars below it under a heading still
marked '(preview)'. Someone following the steps in order never touched a
sidecar. README framed the two as co-equal capabilities.

Restructure so the sidecar loop is the primary path:

- GETTING_STARTED: sidecar creation and the dev loop become Steps 3 and 4,
  skills become Step 5, and build-prompt/review move below the sidecar
  material as 'Team review context'. Drop the '(preview)' label. Group the
  detail sections under a 'Sidecar reference' heading. Lead the auth step
  with CircleCI, since that is what sidecars need.
- README: drop the 'two main capabilities' framing, move context generation
  below the sidecar quick start.
- AGENTS.md: describe sidecars as the primary capability.
- SKILLS.md: lead the intro with sidecar setup and the dev loop, and point
  first-time readers at chunk-sidecar-setup.

Also adds the chunk-sidecar-setup skill to the getting-started skills table,
which listed only four of the five installed skills.

Command-level details in 'Environment setup', 'Syncing', 'First-time sidecar
setup', and 'Hook behavior' are left alone pending #438, #462, #491, and #501,
which change those commands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…init

`chunk sidecar` commands need an org ID, and previously the only way to
get one was to find it in the CircleCI UI and set it by hand. This adds
discovery and auto-detection so most users never have to.

- `chunk org list` (with `--json`) lists the orgs the authenticated user
  belongs to, so the ID is discoverable from the CLI.
- `chunk init` resolves the org ID as part of setup: auto-selects when
  the user belongs to exactly one org, shows a picker when interactive
  with several, and skips silently on auth failure, no TTY, or
  cancellation. `--skip-org-id` opts out.
- Sidecar commands fall back to the same resolution path rather than
  failing with "no interactive terminal".
- chunk-sidecar skill: org preflight now points at `chunk org list
  --json` instead of asking the user for an ID up front.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@danmux
danmux merged commit e5cedfc into main Aug 17, 2026
5 of 6 checks passed
@danmux
danmux deleted the org-id-friction branch August 17, 2026 10:53
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.

3 participants