gastownhall/beads · error

failed to check git index lock %s: %w

Error message

failed to check git index lock %s: %w

What it means

Before staging, gitAddFile resolves the index lock path and calls os.Stat on it. If Stat fails with an error other than NotExist (e.g. permission denied on the .git directory), the lock cannot be verified, so the function fails with this wrapped error rather than guessing. This protects auto-export from staging into an unknown index state.

Source

Thrown at cmd/bd/export_auto.go:851

// gitAddFile stages a file in the enclosing git repo. When called from
// inside a git hook, it scrubs inherited GIT_* env vars (so git
// rediscovers the repo from cwd rather than treating cmd.Dir as the
// worktree root) and skips staging when the target is outside the hook's
// worktree (the .beads/redirect case, where staging would pollute the
// main repo's index). See GH#3311, scrubGitHookEnv, hookWorkTreeRoot.
func gitAddFile(path string) error {
	if wt := hookWorkTreeRoot(); wt != "" && !pathInsideDir(path, wt) {
		// Running inside a hook AND target is outside the hook's worktree.
		// Staging here would pollute a different repo's index; skip.
		return nil
	}

	env := scrubGitHookEnv(os.Environ())
	if lockPath, err := gitIndexLockPath(path, env); err == nil && lockPath != "" {
		if _, statErr := os.Stat(lockPath); statErr == nil {
			return fmt.Errorf("git index is locked at %s; skipping auto-stage", lockPath)
		} else if !os.IsNotExist(statErr) {
			return fmt.Errorf("failed to check git index lock %s: %w", lockPath, statErr)
		}
	} else if err != nil {
		debug.Logf("auto-export: git add lock preflight skipped: %v\n", err)
	}

	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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix permissions on the .git directory so the user running bd can stat files there (chmod/chown as appropriate)
  2. Verify you are operating inside the correct repo/worktree and .git is accessible
  3. If in a worktree/hook context, check that scrubGitHookEnv did not strip needed GIT_DIR context; run bd from the repo root

Example fix

// before
// failed to check git index lock /repo/.git/index.lock: permission denied
// after
sudo chown -R $(whoami) /repo/.git
bd sync
Defensive patterns

Strategy: validation

Validate before calling

// Ensure .git is accessible before running bd
gitDir := ".git"
if _, err := os.Stat(gitDir); err != nil {
    log.Fatal("cannot access .git: ", err)
}
if _, err := os.Stat(filepath.Join(gitDir, "index.lock")); err != nil && !os.IsNotExist(err) {
    log.Fatal("cannot stat index lock (permissions?): ", err)
}

Try / catch

if err := sync(); err != nil && strings.Contains(err.Error(), "failed to check git index lock") {
    // Fix filesystem access, then retry
    fixGitPermissions()
    sync()
}

Prevention

When it happens

Trigger: gitIndexLockPath succeeded and returned a lockPath, but os.Stat(lockPath) returned a non-ENOENT error — e.g. the .git directory or lock file is not readable/stat-able by the current user, or a filesystem error occurred.

Common situations: Running bd as a different user than the repo owner; restrictive permissions on .git; network filesystems returning transient stat errors; sandboxed CI environments blocking .git access.

Related errors


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