gastownhall/beads · error

%w: %s

Error message

%w: %s

What it means

When `git add` exits non-zero, gitAddFile wraps the exec error with git's combined stdout/stderr output so the caller's warning shows the real reason (e.g. "paths are ignored", "Unable to create index.lock") instead of just an exit-status message. The %w preserves the original *exec.ExitError for errors.Is/As checks.

Source

Thrown at cmd/bd/export_auto.go:879

	// .beads/issues.jsonl → pathspec looks under .beads/.beads/) if a caller
	// ever passed a relative path here. Both current callers pass absolute
	// paths, so this guards against a regression rather than fixing a live
	// failure. See GH#4351.
	// Keep cmd.Dir = parent so GH#3311 hook worktree staging still resolves
	// the index path under the repo root (not bare "issues.jsonl" at root).
	cmd := exec.CommandContext(ctx, "git", "add", "--", filepath.Base(path))
	cmd.Dir = filepath.Dir(path)
	cmd.Env = env
	// Capture combined output so the caller's warning surfaces git's stderr
	// (e.g. "paths are ignored", "Unable to create index.lock") instead of
	// just the exit-status text.
	out, err := cmd.CombinedOutput()
	if ctx.Err() == context.DeadlineExceeded {
		return fmt.Errorf("git add timed out after %s", gitAddTimeout)
	}
	if err != nil {
		if trimmed := strings.TrimSpace(string(out)); trimmed != "" {
			return fmt.Errorf("%w: %s", err, trimmed)
		}
		return err
	}
	return nil
}

func gitIndexLockPath(path string, env []string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), gitAddTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, "git", "rev-parse", "--git-dir")
	cmd.Dir = filepath.Dir(path)
	cmd.Env = env
	out, err := cmd.CombinedOutput()
	if ctx.Err() == context.DeadlineExceeded {
		return "", fmt.Errorf("git rev-parse timed out after %s", gitAddTimeout)
	}
	if err != nil {
		if trimmed := strings.TrimSpace(string(out)); trimmed != "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the appended git output in the error to see the exact git failure and address it
  2. If the path is ignored, adjust .gitignore so .beads/issues.jsonl is tracked (or use explicit `git add -f` policy)
  3. Remove a stale .git/index.lock if output says Unable to create index.lock
  4. If the file is missing, rerun `bd sync`/export to regenerate it

Example fix

// before
// exit status 1: The following paths are ignored by one of your .gitignore files: .beads/issues.jsonl
// after
# .gitignore
!.beads/issues.jsonl
bd sync
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the export path is not gitignored before relying on auto-stage
out, err := exec.Command("git", "check-ignore", ".beads/issues.jsonl").Output()
if err == nil && len(out) > 0 {
    fmt.Println("warning: issues.jsonl is gitignored; git add will fail")
}

Type guard

func isExitErrorWithOutput(err error) (*exec.ExitError, bool) {
    var ee *exec.ExitError
    return ee, errors.As(err, &ee)
}

Try / catch

var ee *exec.ExitError
if err := autoExport(); err != nil {
    if errors.As(err, &ee) && strings.Contains(err.Error(), "ignored") {
        // Fix .gitignore so .beads/issues.jsonl is tracked, then retry
    } else if errors.As(err, &ee) && strings.Contains(err.Error(), "index.lock") {
        removeStaleLockAndRetry()
    }
}

Prevention

When it happens

Trigger: gitAddFile's cmd.CombinedOutput() returns err != nil (non-zero exit from git add) with non-empty output; the exit error is wrapped as "%w: %s" with trimmed output appended.

Common situations: The JSONL path is gitignored so `git add` refuses; index.lock contention producing "Unable to create index.lock"; bad path (file deleted between export and staging); git version/config issues in hooks.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/0bfcbe6173645bc6. Report an issue: GitHub.