Bug Description
Claude Code periodically runs git status --short and git log --oneline -n 5 for its status bar/system context. On macOS, git's lstat64 calls on dirty (unstaged) files generate FSEvents notifications. TypeScript's tsc --watch (using the default useFsEvents strategy) picks these up as file changes, triggering infinite recompilation loops approximately every 20 seconds.
Steps to Reproduce
- Open a TypeScript project with
tsc --watch running (or any FSEvents-based file watcher)
- Have unstaged changes in the working directory
- Start Claude Code in the same project directory
- Observe
tsc --watch recompiling every ~20 seconds despite no actual file changes
Root Cause
Git acquires advisory locks and updates file metadata (.git/index.lock, stat calls on dirty files) during git status. These operations generate FSEvents on macOS, which tsc --watch (and other watchers like nodemon, webpack-dev-server, etc.) interpret as file modifications.
Evidence from sudo fs_usage -w -f filesys -e git:
Every ~20 seconds, paired git PIDs spawned by Claude Code run lstat64 on files with unstaged changes. Zero write events — files are only stat'd, never modified. The FSEvents subsystem still emits notifications for these stat operations, causing watchers to trigger.
Specific git commands observed:
git status --short
git log --oneline -n 5
git status --porcelain
Proposed Fix
Add --no-optional-locks flag to all background/polling git commands, OR set GIT_OPTIONAL_LOCKS=0 in the environment of spawned git processes.
From git-status(1):
--no-optional-locks: Do not try to update the index or other files that can be updated as a side effect, if the only purpose is to optimize later git operations.
This is the standard solution for automated/scripted git status polling. It prevents git from:
- Creating
.git/index.lock
- Refreshing the index (which triggers
lstat64 on tracked files)
- Generating FSEvents that confuse file watchers
Implementation options (in order of preference):
- Best: Set
GIT_OPTIONAL_LOCKS=0 in the env when spawning git subprocesses for status/polling
- Alternative: Add
--no-optional-locks flag to all git status and git log invocations used for the status bar
- Minimal: Document the workaround for users
Workaround
Users can add to their shell config:
# ~/.zshrc or ~/.bashrc
export GIT_OPTIONAL_LOCKS=0
# ~/.config/fish/config.fish
set -gx GIT_OPTIONAL_LOCKS 0
This env var is inherited by all child processes (including git spawned by Claude Code) and is safe for all git operations — it only skips opportunistic index refresh, not required locks.
Impact
- Affects all macOS users running file watchers (tsc --watch, nodemon, webpack, vite, etc.) alongside Claude Code
- Causes unnecessary CPU usage from repeated compilations
- Can mask real compilation errors in the noise of phantom rebuilds
- Particularly disruptive for large TypeScript projects where compilation takes several seconds
Environment
- OS: macOS (any version using FSEvents)
- Claude Code: 2.1.42
- tsc: 5.x (any version using
useFsEvents watch strategy, which is the default on macOS)
Related Issues
Bug Description
Claude Code periodically runs
git status --shortandgit log --oneline -n 5for its status bar/system context. On macOS, git'slstat64calls on dirty (unstaged) files generate FSEvents notifications. TypeScript'stsc --watch(using the defaultuseFsEventsstrategy) picks these up as file changes, triggering infinite recompilation loops approximately every 20 seconds.Steps to Reproduce
tsc --watchrunning (or any FSEvents-based file watcher)tsc --watchrecompiling every ~20 seconds despite no actual file changesRoot Cause
Git acquires advisory locks and updates file metadata (
.git/index.lock, stat calls on dirty files) duringgit status. These operations generate FSEvents on macOS, whichtsc --watch(and other watchers like nodemon, webpack-dev-server, etc.) interpret as file modifications.Evidence from
sudo fs_usage -w -f filesys -e git:Every ~20 seconds, paired git PIDs spawned by Claude Code run
lstat64on files with unstaged changes. Zero write events — files are only stat'd, never modified. The FSEvents subsystem still emits notifications for these stat operations, causing watchers to trigger.Specific git commands observed:
git status --shortgit log --oneline -n 5git status --porcelainProposed Fix
Add
--no-optional-locksflag to all background/polling git commands, OR setGIT_OPTIONAL_LOCKS=0in the environment of spawned git processes.From
git-status(1):This is the standard solution for automated/scripted git status polling. It prevents git from:
.git/index.locklstat64on tracked files)Implementation options (in order of preference):
GIT_OPTIONAL_LOCKS=0in the env when spawning git subprocesses for status/polling--no-optional-locksflag to allgit statusandgit loginvocations used for the status barWorkaround
Users can add to their shell config:
This env var is inherited by all child processes (including git spawned by Claude Code) and is safe for all git operations — it only skips opportunistic index refresh, not required locks.
Impact
Environment
useFsEventswatch strategy, which is the default on macOS)Related Issues
.git/index.lockfiles created by CC's background git operations block user git commands #11005 (stale.git/index.lock— same root cause: git lock files from status polling)