This error occurs when Git attempts to create a worktree in a CI/CD environment but the filesystem is mounted as read-only. The fix involves using a writable directory, configuring the CI workspace correctly, or adjusting container mount options.
The "fatal: cannot create worktree: read-only filesystem" error indicates that Git is trying to create a linked worktree on a filesystem that has been mounted in read-only mode. This is particularly common in CI/CD environments where certain directories are intentionally mounted as read-only for security or reproducibility reasons. In CI systems like GitHub Actions, GitLab CI, Jenkins, and CircleCI, the build environment often has a mix of read-only and writable directories: - **Read-only areas**: The container image, system binaries, and sometimes the checkout directory - **Writable areas**: Typically `/tmp`, the workspace directory, and designated cache directories When you run `git worktree add` and Git cannot write to the target location, you'll encounter this error. The filesystem being read-only is often intentional - CI systems use read-only mounts to prevent builds from modifying the base environment and to ensure reproducible builds. This error can also occur when: - A Docker container mounts the repository as read-only - The CI runner uses a read-only filesystem overlay - Security policies (like SELinux or AppArmor) prevent writes - The disk has been remounted as read-only due to filesystem errors
First, identify which directories are writable in your CI environment:
# Check if current directory is writable
touch .write-test && rm .write-test && echo "Writable" || echo "Read-only"
# Check mount options for the filesystem
mount | grep $(df . --output=source | tail -1)
# List common writable directories
for dir in /tmp /workspace $HOME $CI_PROJECT_DIR; do
if [ -d "$dir" ]; then
touch "$dir/.test" 2>/dev/null && rm "$dir/.test" && echo "$dir: writable" || echo "$dir: read-only"
fi
doneThis helps identify where you can safely create worktrees.
Move the worktree target to a directory that's writable in your CI environment:
# Use /tmp which is typically writable
git worktree add /tmp/my-worktree feature-branch
# Or use the CI's designated workspace directory
# GitHub Actions
git worktree add "$GITHUB_WORKSPACE/../worktree" feature-branch
# GitLab CI
git worktree add "$CI_PROJECT_DIR/../worktree" feature-branch
# Jenkins
git worktree add "$WORKSPACE/../worktree" feature-branchNote: Using ../ paths keeps the worktree close to your project while typically being outside the read-only mounted source directory.
If using Docker in your CI pipeline, ensure the repository is mounted with write permissions:
# docker-compose.yml
services:
build:
image: your-image
volumes:
- .:/app:rw # Explicitly set read-write (default, but clear)
# NOT: - .:/app:ro (read-only)# Docker run command
docker run -v "$(pwd):/app:rw" your-image
# If you need the source read-only but want writable worktrees,
# mount a separate writable volume
docker run \
-v "$(pwd):/app:ro" \
-v "/tmp/worktrees:/worktrees:rw" \
your-imageThen create worktrees in /worktrees instead:
git worktree add /worktrees/feature feature-branchFor GitHub Actions, ensure you have write permissions and use the correct directories:
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed for worktrees with full history
- name: Create worktree
run: |
# GitHub Actions workspace is writable
# Create worktree in a sibling directory
git worktree add ../feature-worktree origin/feature-branch
# Or use runner temp directory
git worktree add "$RUNNER_TEMP/worktree" origin/feature-branchThe $RUNNER_TEMP directory is guaranteed to be writable and cleaned up after the job.
For GitLab CI, use the appropriate build directories:
# .gitlab-ci.yml
build:
script:
# GitLab CI's build directory is writable
- git worktree add "$CI_BUILDS_DIR/worktree-$CI_JOB_ID" origin/feature
# Or use the project directory's parent
- git worktree add "$CI_PROJECT_DIR/../feature-worktree" origin/feature
# Clean up after use
- git worktree remove "$CI_BUILDS_DIR/worktree-$CI_JOB_ID" || trueIf using a custom Docker image, ensure it doesn't use --read-only:
build:
image: your-image
variables:
# Some images require this
GIT_STRATEGY: cloneIn Kubernetes environments with readOnlyRootFilesystem: true, you need writable volumes:
# Pod spec
apiVersion: v1
kind: Pod
spec:
containers:
- name: build
securityContext:
readOnlyRootFilesystem: true # Security requirement
volumeMounts:
- name: workspace
mountPath: /workspace
- name: tmp
mountPath: /tmp
- name: worktrees
mountPath: /worktrees
volumes:
- name: workspace
emptyDir: {}
- name: tmp
emptyDir: {}
- name: worktrees
emptyDir: {}Then in your build script:
# Clone to writable workspace
git clone https://github.com/org/repo /workspace/repo
cd /workspace/repo
# Create worktree in writable volume
git worktree add /worktrees/feature origin/featureIf worktrees are problematic in your CI environment, consider using separate clones instead:
# Instead of worktree, use a shallow clone of the specific branch
git clone --depth 1 --branch feature-branch \
"$(git remote get-url origin)" /tmp/feature-checkout
# Or for local changes, use git archive
git archive feature-branch | tar -x -C /tmp/feature-checkout
# For comparing branches, use git diff directly
git diff main...feature-branchThis avoids worktree creation entirely while achieving similar goals.
Sometimes the filesystem becomes read-only due to errors. Check the CI runner's system logs:
# Check kernel messages for filesystem errors
dmesg | grep -i "read-only\|error\|ext4\|xfs" | tail -20
# Check if filesystem was remounted due to errors
mount | grep "ro,"
# Attempt to remount as read-write (requires privileges)
# This is typically done by CI admins, not in build scripts
sudo mount -o remount,rw /path/to/mountIf you see filesystem errors, the CI runner may need maintenance. Report to your DevOps team:
EXT4-fs error (device sda1): ext4_lookup:1604: inode #12345: comm git: deleted inode referenced
EXT4-fs (sda1): Remounting filesystem read-onlyThis indicates disk corruption requiring administrator intervention.
If you're using worktrees to check out a subset of files, sparse checkout might work better in CI:
# Enable sparse checkout
git sparse-checkout init --cone
# Define which directories to include
git sparse-checkout set src/ tests/
# Switch to the branch you need
git checkout feature-branchThis modifies the current checkout rather than creating a new worktree, avoiding the read-only filesystem issue entirely.
For checking out files from another branch without switching:
# Check out specific files from another branch
git checkout feature-branch -- path/to/file.txt
# Or restore from another branch to the working directory
git restore --source=feature-branch -- path/to/file.txtUnderstanding CI filesystem architecture:
Modern CI systems use layered filesystems and containers for isolation:
1. Base image layer (read-only): Contains OS, tools, and dependencies
2. Overlay layer (copy-on-write): Captures changes during build
3. Volume mounts: Can be configured as read-only or read-write
When Git tries to create a worktree, it needs to:
- Create a directory structure
- Write Git metadata files
- Create a HEAD file pointing to the branch
All of these require write permissions to the target directory.
Security considerations:
Read-only filesystems in CI are often intentional security measures:
- Prevents supply chain attacks from modifying build tools
- Ensures reproducible builds
- Limits damage from compromised dependencies
Work within these constraints rather than disabling them. Use designated writable areas like:
- $RUNNER_TEMP (GitHub Actions)
- $CI_BUILDS_DIR (GitLab CI)
- /tmp (usually writable)
Worktree vs. multiple clones performance:
| Approach | Disk Space | Clone Time | Network |
|----------|-----------|------------|---------|
| Worktree | Shared objects | Near instant | None |
| Full clone | Full copy | Long | Full download |
| Shallow clone | Minimal | Fast | Minimal download |
In CI where builds are ephemeral, shallow clones often work as well as worktrees without the filesystem write requirements.
Debugging filesystem issues:
# Get detailed filesystem info
stat -f .
# Check SELinux context (if applicable)
ls -laZ .
# Check AppArmor status
aa-status 2>/dev/null || echo "AppArmor not active"
# Trace system calls to see exact failure
strace -e trace=open,openat,mkdir git worktree add /target branch 2>&1 | grep -i "EROFS\|read-only"Container runtime considerations:
Different container runtimes handle read-only filesystems differently:
- Docker: Use -v /host/path:/container/path:rw
- Podman: Same as Docker, also supports :U for user namespace mapping
- containerd/CRI-O: Configured via Kubernetes PodSpec
CI-specific environment variables for writable paths:
| CI System | Writable Path Variable |
|-----------|----------------------|
| GitHub Actions | $RUNNER_TEMP, $GITHUB_WORKSPACE |
| GitLab CI | $CI_PROJECT_DIR, $CI_BUILDS_DIR |
| CircleCI | $CIRCLE_WORKING_DIRECTORY, /tmp |
| Jenkins | $WORKSPACE, $JENKINS_HOME |
| Azure Pipelines | $(Agent.TempDirectory), $(Build.SourcesDirectory) |
| Travis CI | $TRAVIS_BUILD_DIR, /tmp |
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server
fatal: unable to access: Proxy auto-configuration failed
How to fix 'Proxy auto-configuration failed' in Git
fatal: unable to access: Authentication failed (proxy requires basic auth)
How to fix 'Authentication failed (proxy requires basic auth)' in Git
fatal: unable to access: no_proxy configuration not working
How to fix 'no_proxy configuration not working' in Git
fatal: unable to read tree object in treeless clone
How to fix 'unable to read tree object in treeless clone' in Git