gastownhall/beads · error

git add timed out after %s

Error message

git add timed out after %s

What it means

gitAddFile runs `git add` with a context timeout (gitAddTimeout). If the command does not finish before the deadline (context.DeadlineExceeded), this error is returned. It prevents auto-export from hanging indefinitely on a stuck git process.

Source

Thrown at cmd/bd/export_auto.go:875

	ctx, cancel := context.WithTimeout(context.Background(), gitAddTimeout)
	defer cancel()
	// Pass the basename only, defensively: cmd.Dir is the parent of path, so
	// a full path argument would double-root (cd .beads && git add
	// .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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Clear the contention (remove stale index.lock if no git process is running) and let the next auto-export retry
  2. Check for slow filesystems or hung git processes (ps aux | git) and kill/fix them
  3. Increase gitAddTimeout if your environment legitimately needs longer, or skip auto-stage and run `bd sync`/`git add` manually

Example fix

// before
// git add timed out after 10s
// after
rm -f .git/index.lock   # if stale
git add .beads/issues.jsonl && git commit -m "sync beads"
Defensive patterns

Strategy: retry

Validate before calling

// Detect likely contention before triggering auto-export
if lockExists, _ := exists(filepath.Join(".git", "index.lock")); lockExists {
    return errors.New("git busy: index.lock present, skipping auto-export")
}

Try / catch

err := doExport()
if err != nil && strings.Contains(err.Error(), "git add timed out") {
    // Backoff and retry once
    time.Sleep(5 * time.Second)
    err = doExport()
}

Prevention

When it happens

Trigger: `git add <file>` launched by gitAddFile during auto-export exceeds gitAddTimeout — slow/remote filesystems, a git hook in pre-add, or a blocked index.lock causing git to wait.

Common situations: Large repos on NFS/network drives; another process holding index.lock so git add waits; antivirus or IDE hooks slowing git; heavily loaded CI runners.

Understand the failure class

Related errors


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