# Bare `entire scout` requires a profile selection (no silent default)

`7ebae08`→[main](/content/gh/entireio/cli/commits/main/index.html)·  
  
dipree·1mo ago·4 files·+132 added/-19 removed

A bare invocation no longer auto-runs the default crew:  
\- interactive: a chooser lists profiles (pre-selecting the default) and  
the picked one runs;  
\- non-interactive: it prints the configured profiles and errors, so  
automation must name a profile (`entire scout <name>`) rather than  
silently spawning a default crew.

Explicit `entire scout <name>` and first-run setup are unchanged. Added a  
regression test; updated existing smoke/dispatch tests to name the  
profile explicitly.

## Sessions

6e10ecd1aadfView transcript

## Changes

4

- cmd/entire/cli

- review

- Mcmd.go+33

- Mcmd_test.go+51/-15

- Mpicker.go+44

- Mreview_context_test.go+4/-4

```
660 unmodified lines

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
29 unmodified lines

716
717
718
719
720
721
722
723
724
6 unmodified lines

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
660 unmodified lines

if s == nil {
    s = &settings.EntireSettings{}
}

profileOverride = strings.TrimSpace(profileOverride)
interactiveTTY := interactive.IsTerminalWriter(out) && interactive.CanPromptInteractively()

// Bare `entire scout` never auto-runs a profile. Without a TTY we cannot
// prompt, so list the profiles (or point at setup) and require an explicit
// selection rather than silently spawning a default crew.
if profileOverride == "" && !interactiveTTY {
    cmd.SilenceUsage = true
    eo := cmd.ErrOrStderr()
    if profs := nonZeroProfiles(s.ReviewProfiles); len(profs) > 0 {
        ns := sortedProfileNames(profs)
        fmt.Fprintf(eo, "Specify a profile to scout, e.g. `entire scout %s`.
", ns[0])
        fmt.Fprintf(eo, "Configured profiles: %s\n", strings.Join(ns, ", "))
    } else {
        fmt.Fprintln(eo, "No scout profiles configured. Run `entire scout --configure` in a terminal first.")
    }
    return silentErr(errors.New("no profile specified"))
}

// Trigger first-run setup when no usable profile exists. Counting only
// non-zero profiles means a placeholder/empty entry (e.g. an empty
// `general` profile in a hand-edited preferences file) still routes through
29 unmodified lines

}

// Interactive bare `entire scout` with existing profiles: require a choice
// instead of defaulting silently.
if profileOverride == "" {
    picked, pickErr := promptForProfileToRun(ctx, s)
    if pickErr != nil {
        return handlePickerError(cmd, silentErr, pickErr)
    }
    profileOverride = picked
}

profileName, profile, err := selectReviewProfile(s, profileOverride)
if err != nil {
    cmd.SilenceUsage = true
}

Mcmd/entire/cli/review/cmd.go+33

```
231 unmodified lines

232
233
234
235
235
236
237
238
37 unmodified lines

276
277
278
279
279
280
281
282
30 unmodified lines

313
314
315
316
316
317
318
319
25 unmodified lines

345
346
347
348
348
349
350
351
6 unmodified lines

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
11 unmodified lines

411
412
413
378
414
415
416
417
24 unmodified lines

442
443
444
409
445
446
447
448
116 unmodified lines

565
566
567
532
568
569
570
571
42 unmodified lines

614
615
616
581
617
618
619
620
44 unmodified lines

665
666
667
632
668
669
670
671
59 unmodified lines

731
732
733
698
734
735
736
737
36 unmodified lines

774
775
776
741
777
778
779
780
29 unmodified lines

810
811
812
777
813
814
815
816
25 unmodified lines

842
843
844
809
845
846
847
848
274 unmodified lines

1123
1124
1125
1090
1126
1127
1128
1129
29 unmodified lines

1159
1160
1161
1126
1162
1163
1164
1165

231 unmodified lines

rootCmd := cli.NewRootCmd()
errBuf := &bytes.Buffer{}
rootCmd.SetErr(errBuf)
rootCmd.SetArgs([]string{"review"})
rootCmd.SetArgs([]string{"review", "general"})
err := rootCmd.Execute()
if err == nil {
    t.Fatal("expected error when hooks are not installed")
}
37 unmodified lines

rootCmd := cli.NewRootCmd()
buf := &bytes.Buffer{}
rootCmd.SetOut(buf)
rootCmd.SetArgs([]string{"review"})
rootCmd.SetArgs([]string{"review", "general"})
if err := rootCmd.Execute(); err != nil {
    t.Fatalf("execute: %v", err)
}
30 unmodified lines

rootCmd := cli.NewRootCmd()
errBuf := &bytes.Buffer{}
rootCmd.SetErr(errBuf)
rootCmd.SetArgs([]string{"review"})
rootCmd.SetArgs([]string{"review", "general"})
err := rootCmd.Execute()
if err == nil {
    t.Fatal("expected error when configured skill not installed")
}
25 unmodified lines

rootCmd := cli.NewRootCmd()
buf := &bytes.Buffer{}
rootCmd.SetOut(buf)
rootCmd.SetArgs([]string{"review"})
rootCmd.SetArgs([]string{"review", "general"})
if err := rootCmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
6 unmodified lines

}

// TestRunReview_BareNonInteractiveRequiresProfile verifies that `entire scout`
// with no profile, in a non-interactive context (the test has no TTY), never
// auto-runs a default crew — it errors and lists the configured profiles.
func TestRunReview_BareNonInteractiveRequiresProfile(t *testing.T) {
setupCmdTestRepo(t)
installHooksForCmdTest(t, "cursor")
if err := seedReviewConfig(context.Background(), map[string]settings.ReviewConfig{
    "cursor": {Prompt: "review the diff"},
}); err != nil {
    t.Fatal(err)
}

rootCmd := cli.NewRootCmd()
outBuf := &bytes.Buffer{}
errBuf := &bytes.Buffer{}
rootCmd.SetOut(outBuf)
rootCmd.SetErr(errBuf)
rootCmd.SetArgs([]string{"review"}) // bare, no profile

err := rootCmd.Execute()
if err == nil {
    t.Fatal("bare non-interactive scout should require a profile, got nil error")
}
if !strings.Contains(errBuf.String(), "Specify a profile") {
    t.Errorf("stderr should ask for a profile, got:\n%s", errBuf.String())
}
if !strings.Contains(errBuf.String(), "general") {
    t.Errorf("stderr should list the configured profile, got:\n%s", errBuf.String())
}
// It must not have spawned a crew: no pending marker written.
_, exists, markerErr := review.ReadPendingReviewMarker(context.Background())
if markerErr == nil && exists {
    t.Error("bare non-interactive scout should not have started a review")
}
}

// TestRunReview_FlagOverrideSkipsPicker verifies that --agent flag bypasses
// the interactive picker even when multiple eligible agents are configured.
func TestRunReview_FlagOverrideSkipsPicker(t *testing.T) {
11 unmodified lines

rootCmd := cli.NewRootCmd()
buf := &bytes.Buffer{}
rootCmd.SetOut(buf)
rootCmd.SetArgs([]string{"review", "--agent", "opencode"})
rootCmd.SetArgs([]string{"review", "general", "--agent", "opencode"})
if err := rootCmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
24 unmodified lines

rootCmd := cli.NewRootCmd()
errBuf := &bytes.Buffer{}
rootCmd.SetErr(errBuf)
rootCmd.SetArgs([]string{"review", "--agent", "opencode"})
rootCmd.SetArgs([]string{"review", "general", "--agent", "opencode"})
err := rootCmd.Execute()
if err == nil {
    t.Fatal("expected error when --agent points at hookless agent")
}
116 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(out)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
42 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(buf)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
44 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{"--prompt", "Focus this run on regressions."})
cmd.SetArgs([]string{"general", "--prompt", "Focus this run on regressions."})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
59 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

executeErr := cmd.Execute() // may error (agent-a not a real agent); we only care about picker routing
_ = executeErr              // intentionally ignored: this test only asserts picker routing
36 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(buf)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{"--agent", "cursor"})
cmd.SetArgs([]string{"general", "--agent", "cursor"})

// cursor is not launchable in our stub (reviewerFor returns nil), so it
// falls through to RunMarkerFallback. That's fine — we only care that
29 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(errBuf)
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

err := cmd.Execute()
if err != nil {
25 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
274 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(buf)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
29 unmodified lines

cmd := review.NewCommand(deps)
cmd.SetOut(buf)
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{})
cmd.SetArgs([]string{"general"})

if err := cmd.Execute(); err != nil {
    t.Fatalf("unexpected error: %v", err)
}
```
