Skip to content

Commit 534500d

Browse files
committed
🤖 fix: git polling defers to agent commands via flock coordination
Problem: Git fetch polling would hold locks that caused agent git operations (git add, commit, checkout, etc.) to fail with 'another git process' errors. Solution: Use flock-based coordination between polling and agent commands: 1. Agent bash commands acquire a SHARED lock on .git/mux.lock - Multiple agent commands can run concurrently (shared locks compatible) - Lock acquired at start, released when command exits 2. Git fetch polling tries to acquire EXCLUSIVE lock (non-blocking) - If agents are running (shared locks held), fetch skips this cycle - Polling retries on next cycle (3s later with exponential backoff) This ensures: - Agent operations are never blocked by polling - Multiple agent commands can run concurrently - Polling only runs when workspace is idle Also adds belt-and-suspenders check for git's own lock files. _Generated with `mux`_
1 parent 59be8be commit 534500d

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

src/common/utils/git/gitStatus.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,34 @@ export function parseGitStatusScriptOutput(output: string): ParsedGitStatusOutpu
8686
*
8787
* Environment variables disable all interactive prompts (keychain, SSH, credentials).
8888
* Git flags optimize for speed - only fetch refs, not objects.
89+
*
90+
* Lock coordination: Uses flock to coordinate with agent bash commands.
91+
* - Fetch acquires EXCLUSIVE lock (non-blocking) - skips if can't acquire
92+
* - Agent commands acquire SHARED lock (see bash tool) - can run concurrently
93+
* This ensures fetch never blocks agent operations - it just defers to next cycle.
8994
*/
9095
export const GIT_FETCH_SCRIPT = `
96+
# Coordinate with agent commands via flock
97+
# Fetch uses exclusive lock (non-blocking) - skips if agents are running
98+
# Agent commands use shared lock - they can run concurrently with each other
99+
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 1
100+
LOCK_FILE="$GIT_DIR/mux.lock"
101+
102+
exec 200>"$LOCK_FILE"
103+
if ! flock --nonblock --exclusive 200; then
104+
echo "SKIP: workspace in use by agent"
105+
exit 0
106+
fi
107+
108+
# Also skip if git's own lock files exist (belt and suspenders)
109+
GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null) || GIT_COMMON_DIR="$GIT_DIR"
110+
for DIR in "$GIT_DIR" "$GIT_COMMON_DIR"; do
111+
if find "$DIR" -name "*.lock" -type f 2>/dev/null | grep -q .; then
112+
echo "SKIP: git lock files present"
113+
exit 0
114+
fi
115+
done
116+
91117
# Disable ALL prompts
92118
export GIT_TERMINAL_PROMPT=0
93119
export GIT_ASKPASS=echo

src/node/services/tools/bash.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ import type { BashToolResult } from "@/common/types/tools";
1616
import type { ToolConfiguration, ToolFactory } from "@/common/utils/tools/tools";
1717
import { TOOL_DEFINITIONS } from "@/common/utils/tools/toolDefinitions";
1818

19+
/**
20+
* Bash script prefix to acquire shared lock on git workspace.
21+
* Coordinates with git polling (GIT_FETCH_SCRIPT) which uses exclusive lock.
22+
* - Agent commands: shared lock (can run concurrently with each other)
23+
* - Git fetch polling: exclusive lock (non-blocking, skips if agents running)
24+
* This ensures polling never blocks agent operations.
25+
*/
26+
const GIT_COORDINATION_LOCK_PREFIX = `
27+
# Acquire shared lock on git workspace to coordinate with background polling
28+
# This prevents git fetch from running while agent commands execute
29+
if GIT_DIR=$(git rev-parse --git-dir 2>/dev/null); then
30+
exec 200>"$GIT_DIR/mux.lock"
31+
flock --shared 200
32+
fi
33+
`;
34+
1935
/**
2036
* Validates bash script input for common issues
2137
* Returns error result if validation fails, null if valid
@@ -242,7 +258,9 @@ export const createBashTool: ToolFactory = (config: ToolConfiguration) => {
242258
const truncationState = { displayTruncated: false, fileTruncated: false };
243259

244260
// Execute using runtime interface (works for both local and SSH)
261+
// Prefix with git coordination lock (shared) and stdin closure
245262
const scriptWithClosedStdin = `exec </dev/null
263+
${GIT_COORDINATION_LOCK_PREFIX}
246264
${script}`;
247265
const execStream = await config.runtime.exec(scriptWithClosedStdin, {
248266
cwd: config.cwd,

0 commit comments

Comments
 (0)