# fix(trail): flag onboarding-created runners left un-tailored

`9ddaef9`·

Soph·3w ago·5 files·+88 added/-27 removed

Onboarding writes the default runner set, then tune tailors in place. If a runner's tailoring is skipped, omitted by the model, or returned verbatim, it stays a generic default — and the previous "Updated N… review with git diff" nudged the user to commit those generic placeholders as if they were repo-specific.

ensureRunnersPresent now reports which runners it created; applyTuneWithAgent tracks which were actually tailored and warns about any created-but-untailored ("…still generic defaults, not tailored to this repo: …; re-run or edit/remove before committing"). The --print path likewise notes that freshly-created defaults are untracked and not yet tailored.

## Sessions

d87e5916f664View transcript

## Changes

5

- cmd/entire/cli

- Mtrail_tune_apply.go+15
  - Mtrail_tune_apply_test.go+18
  - Mtrail_tune_cmd.go+29/-12
  - Mtrail_tune_init.go+16/-13
  - Mtrail_tune_init_test.go+10/-2

```go

// untailoredRunners returns the created runner IDs that tuning did NOT tailor
// (still generic defaults), sorted. These were scaffolded by onboarding but
// left unchanged — skipped, omitted by the model, or returned verbatim — so
// they must not be presented as repo-tailored.
func untailoredRunners(createdIDs []string, tailored map[string]bool) []string {
	var out []string
	for _, id := range createdIDs {
		if !tailored[normalizeRunnerID(id)] {
			out = append(out, id)
		}
	}
	sort.Strings(out)
	return out
}

// droppedPlaceholders returns the placeholders present in oldTemplate but not in
// newTemplate, sorted. Used to inform the user when a rewrite stops using one.
func droppedPlaceholders(oldTemplate, newTemplate string) []string {
```

```go
func TestUntailoredRunners(t *testing.T) {
	t.Parallel()

created := []string{"trail-risk", "trail-drift", "trail-review"}
	tailored := map[string]bool{"risk": true} // normalized IDs (no "trail-" prefix)

got := untailoredRunners(created, tailored)
	want := []string{"trail-drift", "trail-review"}
	if len(got) != len(want) || got[0] != want[0] || got[1] != want[1] {
			t.Errorf("untailoredRunners = %v, want %v", got, want)
	}

// Nothing created → nothing untailored, even with no tailoring recorded.
	if u := untailoredRunners(nil, map[string]bool{}); len(u) != 0 {
		t.Errorf("expected empty, got %v", u)
	}
}

func TestParseTuneOutput(t *testing.T) {
	t.Parallel()
```

```go
// ensureRunnersPresent scaffolds the default runner set when a repo has none
// yet, so `tune` doubles as onboarding. It is a no-op when runners already
// exist, and returns an error when the user declined or creation failed.
// Writing is gated on confirmation (interactive prompt, or the --yes flag for
// non-interactive runs).
func ensureRunnersPresent(w, errW io.Writer, repoRoot string, assumeYes bool) (created []string, err error) {
	dir := runnersDir(repoRoot)
	existing, _ := filepath.Glob(filepath.Join(dir, "*.json")) //nolint:errcheck // bad pattern only; treated as "none found"
	if len(existing) > 0 {
		return nil, nil
	}

defaults, err := runnerdefaults.Files()
	if err != nil {
		return nil, fmt.Errorf("loading default runners: %w", err)
	}

if !assumeYes {
		if !interactive.CanPromptInteractively() {
			return fmt.Errorf("no runner configs found under %s; re-run with --yes to create the default set (%d runners)", dir, len(defaults))
		}
		confirmed, err := confirmCreateRunners(len(defaults))
		if err != nil {
			return nil, err
		}
		if !confirmed {
			return nil, errors.New("no runner configs created (declined)")
		}
	}

if err := os.MkdirAll(dir, 0o755); err != nil { //nolint:gosec // config dir, conventional perms
		return fmt.Errorf("creating %s: %w", dir, err)
	}
	for _, f := range defaults {
		dest := filepath.Join(dir, f.Name)
		if err := os.WriteFile(dest, f.Data, 0o644); err != nil { //nolint:gosec // runner configs are repo-committed, world-readable config
			return fmt.Errorf("writing %s: %w", dest, err)
		}
		fmt.Fprintf(w, "created %s\n", filepath.Join(paths.EntireDir, "runners", f.Name))
		created = append(created, strings.TrimSuffix(f.Name, ".json"))
	}
	fmt.Fprintf(errW, "Created %d default runner(s); tailoring them to this repo…\n", len(defaults))
	return created, nil
}

func confirmCreateRunners(n int) (bool, error) {}
```
