{"record":{"id":"241525411c46a6c5","repo":"gastownhall/beads","slug":"git-index-is-locked-at-s-skipping-auto-stage","errorCode":null,"errorMessage":"git index is locked at %s; skipping auto-stage","messagePattern":"git index is locked at (.+?); skipping auto-stage","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/bd/export_auto.go","lineNumber":849,"sourceCode":"}\n\n// gitAddFile stages a file in the enclosing git repo. When called from\n// inside a git hook, it scrubs inherited GIT_* env vars (so git\n// rediscovers the repo from cwd rather than treating cmd.Dir as the\n// worktree root) and skips staging when the target is outside the hook's\n// worktree (the .beads/redirect case, where staging would pollute the\n// main repo's index). See GH#3311, scrubGitHookEnv, hookWorkTreeRoot.\nfunc gitAddFile(path string) error {\n\tif wt := hookWorkTreeRoot(); wt != \"\" && !pathInsideDir(path, wt) {\n\t\t// Running inside a hook AND target is outside the hook's worktree.\n\t\t// Staging here would pollute a different repo's index; skip.\n\t\treturn nil\n\t}\n\n\tenv := scrubGitHookEnv(os.Environ())\n\tif lockPath, err := gitIndexLockPath(path, env); err == nil && lockPath != \"\" {\n\t\tif _, statErr := os.Stat(lockPath); statErr == nil {\n\t\t\treturn fmt.Errorf(\"git index is locked at %s; skipping auto-stage\", lockPath)\n\t\t} else if !os.IsNotExist(statErr) {\n\t\t\treturn fmt.Errorf(\"failed to check git index lock %s: %w\", lockPath, statErr)\n\t\t}\n\t} else if err != nil {\n\t\tdebug.Logf(\"auto-export: git add lock preflight skipped: %v\\n\", err)\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), gitAddTimeout)\n\tdefer cancel()\n\t// Pass the basename only, defensively: cmd.Dir is the parent of path, so\n\t// a full path argument would double-root (cd .beads && git add\n\t// .beads/issues.jsonl → pathspec looks under .beads/.beads/) if a caller\n\t// ever passed a relative path here. Both current callers pass absolute\n\t// paths, so this guards against a regression rather than fixing a live\n\t// failure. See GH#4351.\n\t// Keep cmd.Dir = parent so GH#3311 hook worktree staging still resolves\n\t// the index path under the repo root (not bare \"issues.jsonl\" at root).\n\tcmd := exec.CommandContext(ctx, \"git\", \"add\", \"--\", filepath.Base(path))","sourceCodeStart":831,"sourceCodeEnd":867,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/export_auto.go#L831-L867","documentation":"gitAddFile runs a preflight that resolves the git index lock path (via `git rev-parse --git-dir`) and checks whether index.lock exists. If the lock file is present, it skips staging and returns this error so auto-export does not fight a concurrent git operation. It is a deliberate skip, not a crash of the export itself.","triggerScenarios":"gitAddFile (called from maybeAutoExport during auto-export) finds an existing index.lock in the resolved git dir at the moment it tries to stage the JSONL — typically while another git process (commit, rebase, another bd hook) holds the index.","commonSituations":"A git GUI, IDE, or parallel `git commit` is running when bd's post-hook fires; a stale index.lock left by a crashed git process; two bd instances auto-exporting concurrently.","solutions":["Wait for the concurrent git operation to finish and let the next auto-export stage the file","If no git process is running, remove the stale lock: `rm -f .git/index.lock` (or the path shown in the error), then retry","Run `bd sync` / explicit export to force staging once the lock clears"],"exampleFix":"// before\n// git index is locked at /repo/.git/index.lock; skipping auto-stage\n// after\nrm -f /repo/.git/index.lock   # only if no git process is actually running\nbd sync","handlingStrategy":"retry","validationCode":"// Preflight: check for a live index.lock before exporting\nif _, err := os.Stat(filepath.Join(gitDir, \"index.lock\")); err == nil {\n    fmt.Println(\"git index locked; wait for the git operation to finish\")\n}","typeGuard":null,"tryCatchPattern":"if err := autoExport(); err != nil && strings.Contains(err.Error(), \"index is locked\") {\n    time.Sleep(2 * time.Second)\n    err = autoExport() // retry after the concurrent git op likely finished\n}","preventionTips":["Don't run concurrent git mutations (GUI commits, rebases) while bd hooks fire","Clean up stale index.lock files after crashed git processes","Serialize bd auto-export with other git automation in CI","Check `git status` health before long sync sessions"],"tags":["git","auto-export","index-lock"],"backgroundTag":"git-index-locked","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}