From 3f9428640d6899cfa8f17e86c693a084a61611a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 09:43:58 +0000 Subject: [PATCH] fix(daemon): prevent git index.lock conflicts during background polling Set GIT_OPTIONAL_LOCKS=0 on all git commands in GetGitInfo() to avoid acquiring optional locks that can conflict with user-initiated git operations. The daemon polls git status every 3 seconds, and without this flag, `git status` takes an optional lock on the index to refresh cached stat info, which can cause "Unable to create '.git/index.lock'" errors for concurrent user operations. https://claude.ai/code/session_01QqiZJAKYLKtTxLZt7tREhu --- daemon/git.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/daemon/git.go b/daemon/git.go index 2a7b034..8079a93 100644 --- a/daemon/git.go +++ b/daemon/git.go @@ -2,6 +2,7 @@ package daemon import ( "context" + "os" "os/exec" "strings" "time" @@ -16,6 +17,16 @@ type GitInfo struct { IsRepo bool } +// gitCmd creates a git command that won't acquire optional locks. +// This prevents conflicts with user-initiated git operations when the daemon +// polls git status in the background. Uses GIT_OPTIONAL_LOCKS=0 which is +// equivalent to passing --no-optional-locks to every git command. +func gitCmd(ctx context.Context, args ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, "git", args...) + cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") + return cmd +} + // GetGitInfo returns git branch and dirty status for a directory. // It uses the native git CLI which is significantly faster and more memory-efficient // than the pure-Go go-git implementation, especially on large repositories. @@ -28,19 +39,19 @@ func GetGitInfo(workingDir string) GitInfo { defer cancel() // Check if this is a git repo - if err := exec.CommandContext(ctx, "git", "-C", workingDir, "rev-parse", "--git-dir").Run(); err != nil { + if err := gitCmd(ctx, "-C", workingDir, "rev-parse", "--git-dir").Run(); err != nil { return GitInfo{} } info := GitInfo{IsRepo: true} // Get branch name from HEAD - if out, err := exec.CommandContext(ctx, "git", "-C", workingDir, "rev-parse", "--abbrev-ref", "HEAD").Output(); err == nil { + if out, err := gitCmd(ctx, "-C", workingDir, "rev-parse", "--abbrev-ref", "HEAD").Output(); err == nil { info.Branch = strings.TrimSpace(string(out)) } // Check dirty status via porcelain output (empty = clean) - if out, err := exec.CommandContext(ctx, "git", "-C", workingDir, "status", "--porcelain").Output(); err == nil { + if out, err := gitCmd(ctx, "-C", workingDir, "status", "--porcelain").Output(); err == nil { info.Dirty = len(strings.TrimSpace(string(out))) > 0 }