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
- Clear the contention (remove stale index.lock if no git process is running) and let the next auto-export retry
- Check for slow filesystems or hung git processes (ps aux | git) and kill/fix them
- 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
- Keep repos on local SSDs where possible; avoid NFS for .git
- Raise gitAddTimeout in slow/CI environments
- Investigate hung git processes if timeouts recur
- Exclude repo directories from real-time antivirus scanning
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- git ls-remote %s failed: %s: %w
- auto-export: git add failed: %w
- git index is locked at %s; skipping auto-stage
- failed to check git index lock %s: %w
- %w: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/1407d1830f32912d.
Report an issue: GitHub.