# surface git stderr from common-dir resolution

`5362c37`→[main](/content/gh/entireio/cli/commits/main/index.html)·  
  
pfleidi·1w ago·2 files·+28 added/-9 removed

Extract the ExitError.Stderr formatting into gitOutputError and use it
for both git invocations that parse stdout. Align the ignore-rule doc
comment with the append-only-to-existing-.gitignore behavior.

## Sessions

01KX700A428KXQ7V4WFPBHZ6X4View transcript

[?\  
Add trail checkout --worktree SupportClaude Code·3 steps](/content/gh/entireio/cli/session/c5215fe7-28db-4a83-9d2e-482217df292d#timeline-01KX700A428KXQ7V4WFPBHZ6X4/index.html)

## Changes

2

- cmd/entire/cli  
  
  - Mtrail_checkout_worktree.go+18/-9  
  
  - Mtrail_checkout_worktree_test.go+10

```
53 unmodified lines

54
55
56
57
57
58
59
60
17 unmodified lines

78
79
80
81
82
83
81
82
83
84
85
86
87
88
332 unmodified lines

421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
12 unmodified lines

450
451
452
440
441
442
443
444
453
454
455
456

53 unmodified lines

cmd := exec.CommandContext(ctx, "git", "rev-parse", "--git-common-dir")
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("failed to get git common dir: %w", err)
		return "", gitOutputError("failed to get git common dir", err)
	}
	gitDir := strings.TrimSpace(string(output))
	if !filepath.IsAbs(gitDir) {
17 unmodified lines

return filepath.Dir(gitDir), nil
	}

// ensureTrailWorktreeIgnoreRule makes sure .entire/worktrees/ is git-ignored.
// Already ignored (any mechanism) → silent no-op. Otherwise the rule is
// appended to the repo-root .gitignore; committing it is the user's choice.
// ensureTrailWorktreeIgnoreRule appends the .entire/worktrees/ rule to an
// existing repo-root .gitignore when the directory isn't already ignored.
// Already ignored, or no .gitignore at all → silent no-op: the CLI doesn't
// impose ignore policy on a repo that hasn't opted into one, and committing
// the appended rule stays the user's choice.
func ensureTrailWorktreeIgnoreRule(ctx context.Context, w io.Writer, root string) error {
	check := exec.CommandContext(ctx, "git", "check-ignore", "-q", trailWorktreesRelDir+"/")
	check.Dir = root
332 unmodified lines

return nil
}

// gitOutputError formats a failed git invocation, including git's stderr
// (captured by cmd.Output in ExitError.Stderr) when it carries a diagnostic —
// the bare error is usually just "exit status 128".
func gitOutputError(action string, err error) error {
	var exitErr *exec.ExitError
	if errors.As(err, &exitErr) && len(bytes.TrimSpace(exitErr.Stderr)) > 0 {
		return fmt.Errorf("%s: %s: %w", action, bytes.TrimSpace(exitErr.Stderr), err)
	}
	return fmt.Errorf("%s: %w", action, err)
}

func staleTrailWorktreeError(branch, path string) error {
	return fmt.Errorf("branch %q is registered to a missing worktree at %s; run 'git worktree prune' to clear it", branch, path)
}
12 unmodified lines

cmd.Dir = root
	output, err := cmd.Output()
	if err != nil {
		var exitErr *exec.ExitError
		if errors.As(err, &exitErr) && len(bytes.TrimSpace(exitErr.Stderr)) > 0 {
			return trailWorktreeMatch{}, false, fmt.Errorf("failed to list worktrees: %s: %w", bytes.TrimSpace(exitErr.Stderr), err)
		}
		return trailWorktreeMatch{}, false, fmt.Errorf("failed to list worktrees: %w", err)
		return trailWorktreeMatch{}, false, gitOutputError("failed to list worktrees", err)
	}
	// Empty currentRoot: match any worktree, including the current checkout.
	path, found := parseWorktreeForBranch(string(output), branch, "")
```

Mcmd/entire/cli/trail_checkout_worktree.go+18/-9

```

642 unmodified lines

643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658

642 unmodified lines

}
}

func TestGitCommonDirForTrailWorktree_SurfacesGitError(t *testing.T) {
	testutil.IsolateGitConfigEnv(t)
	t.Chdir(t.TempDir())

_, err := gitCommonDirForTrailWorktree(context.Background())
	if err == nil || !strings.Contains(err.Error(), "not a git repository") {
		t.Fatalf("error = %v, want git stderr in message", err)
	}
}

func TestCheckoutTrailWorktree_RejectsInvalidBranch(t *testing.T) {
	t.Parallel()
