Skip to content

Instantly share code, notes, and snippets.

@raymondbutcher
Created July 3, 2026 13:37
Show Gist options
  • Select an option

  • Save raymondbutcher/af50bd63e40c0ffc6dc1e63707eebb2b to your computer and use it in GitHub Desktop.

Select an option

Save raymondbutcher/af50bd63e40c0ffc6dc1e63707eebb2b to your computer and use it in GitHub Desktop.
docker desktop virtiofs bug when using git rebase
#!/bin/sh
# rebase.sh — runs a git rebase, either natively or inside Docker.
#
# Runs a plain `git rebase` with no external tools or file modifications.
# If the racy-git/VirtioFS bug is present, the rebase will fail with:
# error: Your local changes to the following files would be overwritten by merge
# ...despite git status showing clean.
set -e
cd "${REPO_DIR:-/repo}"
echo "=== Initial status ==="
git status
echo ""
echo "=== Running: git checkout feature; git rebase main ==="
git checkout feature
REBASE_OUT=$(git rebase main 2>&1) && REBASE_STATUS=0 || REBASE_STATUS=$?
printf '%s\n' "$REBASE_OUT"
if [ "$REBASE_STATUS" -eq 0 ]; then
echo ""
echo "Rebase succeeded — bug not reproduced."
elif printf '%s\n' "$REBASE_OUT" | grep -q "would be overwritten by merge"; then
echo ""
echo "████ REPRODUCED ████"
echo ""
echo "git status at failure:"
git status
else
echo ""
echo "Rebase failed for a different reason (not the racy-git bug)."
echo ""
echo "git status at failure:"
git status
exit 1
fi
#!/usr/bin/env bash
# reproduce.sh — demonstrates a git rebase failure inside Docker on macOS.
#
# What happened
# ─────────────
# During a plain `git rebase` run inside a Docker container (repo bind-mounted
# from a macOS host), the rebase failed with:
#
# error: Your local changes to the following files would be overwritten by merge
#
# ...even though `git status` showed "nothing to commit, working tree clean."
# No external tools were running or modifying files.
#
# Suspected cause
# ───────────────
# Git uses file mtimes to detect whether working-tree files differ from the
# index (the "racy git" problem). Docker bind mounts on macOS (VirtioFS) have
# coarser timestamp resolution (~1 second) than native Linux filesystems
# (nanoseconds). When git rapidly applies commits during a rebase, the
# coarsened mtimes may confuse racy-git detection.
#
# The root cause has not been confirmed, but the bug is reproducible via this
# script.
#
# The script runs rebase.sh twice against a fresh repo each time:
# Run 1 — natively on the host (control): rebase should succeed.
# Run 2 — inside Docker with a VirtioFS bind mount: rebase should fail.
#
# Usage
# ─────
# bash reproduce.sh
#
# The test repo is left at ./racy-test-repo after the run for inspection.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$SCRIPT_DIR/racy-test-repo"
command -v docker >/dev/null 2>&1 || { echo "Error: docker not found"; exit 1; }
command -v git >/dev/null 2>&1 || { echo "Error: git not found"; exit 1; }
# ── Run 1: native ────────────────────────────────────────────────────────────
echo "=== Setting up repository ==="
bash "$SCRIPT_DIR/setup-repo.sh" "$REPO"
echo ""
git -C "$REPO" log --oneline --all --graph
echo ""
echo "=== Run 1: native — expect success ==="
echo ""
REPO_DIR="$REPO" sh "$SCRIPT_DIR/rebase.sh"
# ── Run 2: Docker ────────────────────────────────────────────────────────────
echo ""
echo "=== Setting up repository ==="
bash "$SCRIPT_DIR/setup-repo.sh" "$REPO"
echo ""
git -C "$REPO" log --oneline --all --graph
echo ""
echo "=== Run 2: inside Docker (bind-mounted from host) — expect failure ==="
echo ""
docker run --rm \
-v "$REPO:/repo" \
-v "$SCRIPT_DIR/rebase.sh:/rebase.sh" \
-w /repo \
docker/sandbox-templates:claude-code \
sh /rebase.sh
#!/usr/bin/env bash
# setup-repo.sh — creates the test git repository used by reproduce.sh.
#
# Usage: bash setup-repo.sh <repo-path>
set -euo pipefail
REPO="${1:?Usage: setup-repo.sh <repo-path>}"
rm -rf "$REPO"
mkdir -p "$REPO/internal/fuse"
cd "$REPO"
git init -q
git config user.email "test@example.com"
git config user.name "Test"
# Commit A — shared base
cat > internal/fuse/mount.go << 'EOF'
package fuse
// Mount attaches the filesystem.
func Mount(path string) error {
return nil
}
EOF
git add -A
git commit -q -m "A: initial"
A=$(git rev-parse HEAD)
# Commit B — main branch diverges (separate file to avoid rebase conflicts)
cat > internal/fuse/unmount.go << 'EOF'
package fuse
// Unmount detaches the filesystem.
func Unmount(path string) error {
return nil
}
EOF
git add -A
git commit -q -m "B: main adds Unmount"
# Feature branch: two commits off A
git checkout -q -b feature "$A"
cat >> internal/fuse/mount.go << 'EOF'
// Remount re-applies options without detaching.
func Remount(path string) error {
return nil
}
EOF
git add -A
git commit -q -m "C: feature adds Remount"
cat >> internal/fuse/mount.go << 'EOF'
// Stat returns filesystem statistics.
func Stat(path string) error {
return nil
}
EOF
git add -A
git commit -q -m "D: feature adds Stat"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment