Skip to content

Add parallel sidecars - #451

Draft
schurchleycci wants to merge 4 commits into
mainfrom
parallel-sidecars
Draft

Add parallel sidecars#451
schurchleycci wants to merge 4 commits into
mainfrom
parallel-sidecars

Conversation

@schurchleycci

@schurchleycci schurchleycci commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds --count N to chunk sidecar create to spin up multiple sidecars in parallel and set them as the active group
  • Updates chunk sidecar sync to deliver one bundle to all sidecars in the active group in parallel
  • Updates chunk validate to automatically provision one sidecar per remote command when two or more remote commands are configured (reuses an existing group of the right size)
  • Fixes three bugs discovered during implementation: nil check on active in resolveSidecarID, missing error path in sync fan-out, and a race in parallel create result collection

Test plan

  • chunk sidecar create --count 3 creates three sidecars and sets them as the active group
  • chunk sidecar sync delivers the bundle to all sidecars in the active group in parallel
  • chunk validate with two or more remote commands creates matching sidecars and runs them in parallel
  • Single-sidecar workflows are unaffected (no --count, no group — existing behaviour unchanged)
  • Unit tests pass: task test
  • Acceptance tests pass: task acceptance-test

🤖 Generated with Claude Code

schurchleycci and others added 3 commits July 24, 2026 16:55
Introduce running work across multiple sidecars at once:

- chunk sidecar create --count N creates several sidecars in parallel and
  sets them as the active group (a single sidecar is just a group of one).
- chunk sidecar run fans a command out across the active group (or an
  explicit --sidecar-ids set), with per-sidecar labelled output, an N/M
  passed summary, and --json results.
- chunk sidecar sync builds one bundle and delivers it to every sidecar in
  the group in parallel.
- chunk validate distributes remote-marked commands across a group of
  sidecars (one per command) and runs them in parallel, reusing an existing
  active group when the count matches, then runs local commands. Falls back
  to local execution on unreachable/misconfigured sidecars unless freshly
  created.

Active-sidecar state stores a SidecarIDs slice (with legacy sidecar_id read
compatibility) so single- and multi-sidecar flows share one representation
and code path throughout sync, state, and the validate executor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- sidecar sync: route single-sidecar active state through incremental
  BundleSync instead of BundleSyncFanOut (len > 0 → len > 1), avoiding
  a full-bundle regression for existing single-sidecar users
- setupSidecarGroup: save partially-created sidecars before returning
  an error so they aren't orphaned on partial creation failure
- --checkout error: add suggestion pointing to --sidecar-id workaround

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Will be superseded by the async exec rewrite; the parallel validate
and sync fan-out work in this PR stands on its own without it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread internal/cmd/sidecar.go
if err != nil {
return err
}
if len(ids) > 1 {

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.

--sidecar-ids with exactly one ID is silently ignored. When resolveSidecarGroupIDs returns a one-element slice, this branch is skipped and execution falls through to resolveSidecarID, which ignores the provided ID and resolves from --sidecar-id (empty) or the active sidecar instead.

Concrete failure: chunk sidecar sync --sidecar-ids=sb-target with a different active sidecar syncs to the wrong sidecar with no warning.

Suggested change
if len(ids) > 1 {
if len(ids) == 1 {
sidecarID = ids[0]
} else if err := resolveSidecarID(cmd.Context(), &sidecarID); err != nil {
return err
}

Comment thread internal/cmd/sidecar.go Outdated
io.ErrPrintf("%s\n", ui.Success(fmt.Sprintf("Deleted sidecar %s", sidecarID)))

if active, lerr := sidecar.LoadActive(cmd.Context()); lerr == nil && active != nil && active.SidecarID == sidecarID {
if active, lerr := sidecar.LoadActive(cmd.Context()); lerr == nil && active != nil && active.ID() == sidecarID {

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.

Deleting a non-primary sidecar from an active group leaves stale active state. This check clears the active file only when the deleted ID matches active.ID() (the first element). For an active group [sb-1, sb-2], deleting sb-2 leaves the group file intact — subsequent sync or validate calls fan-out to both IDs and fail on the deleted one.

Should check whether sidecarID appears anywhere in active.SidecarIDs, not just the primary slot. Same issue exists in the snapshot delete command (~line 951).

Comment thread internal/cmd/validate.go
authSock := os.Getenv(config.EnvSSHAuthSock)
err := sidecar.BundleSyncFanOut(ctx, client, opts.sidecarIDs, opts.identityFile, authSock, opts.workdir, workDir, statusFn)
if err != nil {
return &userError{msg: "Could not sync to sidecars.", err: err}

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.

syncToSidecarsOpts swallows the NoOriginRemoteError hint. All errors from BundleSyncFanOut are wrapped with the generic "Could not sync to sidecars." message, but the single-sidecar sync path and newSidecarSyncCmd's fan-out path both check for this error type and surface msgNoOriginRemote + suggestionAddOrigin. Group-mode validate in a repo without origin gives no actionable guidance.

Suggested change
return &userError{msg: "Could not sync to sidecars.", err: err}
if _, ok := errors.AsType[*sidecar.NoOriginRemoteError](err); ok {
return &userError{msg: msgNoOriginRemote, suggestion: suggestionAddOrigin, err: err}
}
return &userError{msg: "Could not sync to sidecars.", err: err}

… only checks primary ID, NoOriginRemoteError swallowed in group sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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