Guildwise HQ

← All articles

Keeping git writes working when the home config is read-only

The Guildwise HQ team · · 5 min read

Abstract branded cover showing a locked file card beside a open writable path, in muted blues and grays

Why a read-only home file stops git

Git reads its settings from several scopes. The system file applies to every user on the machine. The global file applies to one user and usually sits in the home directory. The repository file lives inside the .git directory. Git also reads a per-worktree file when that feature is enabled, and it accepts settings from the environment and from the command line. The official configuration documentation describes these scopes and the order in which they are applied.

Reading the global file is harmless on a read-only mount. Writing to it is not. When a command changes a setting, such as a global identity or the upstream of a new branch, git must write the new value to the file. It first takes a lock on that file. If the mount refuses the write, the command fails, usually with a message that git could not lock the config file.

The failure only appears when a write is attempted. A job can pass its checkout, its tests and its diff step, then fail on the one step that records a setting. That is the pattern to design for. Checking for it at the start of a task is cheaper than finding it at the end.

Read the error to find the failing scope

The error message names the file git tried to change. That path tells you which fix applies:

  • A path in the home directory or the XDG config directory means the global scope. Use one of the options below that redirects or bypasses this file.
  • A path inside the repository's .git directory means the repository scope. Redirecting the global file will not help, because the repository file is still written.
  • A path in the system configuration location means the system scope. For a sandbox, the usual approach is to skip that file for the job rather than try to write it.

If the workspace itself is mounted read-only, no configuration setting will fix the problem. Git also writes its index and object files during normal work. In that case the repository has to be cloned or checked out into a writable location, and the build has to write its output there too.

Option 1: point git at a writable global file

The cleanest fix is to move the global scope to a path you control. The git documentation describes an environment variable for this purpose. When it is set, git reads and writes that file in place of the usual global file. The documentation also says the variable can be set to a null device to skip the global level entirely.

At the start of the task, create a scratch file on a writable volume and point the variable at it:

export GIT_CONFIG_GLOBAL=/scratch/task/gitconfig git config --global user.name 'Build Agent' git config --global user.email '[email protected]'

Two points need care. The original home file is no longer read, so anything the task depends on, such as credential helpers or aliases, must be copied into the scratch file. The scratch file also lasts only as long as its volume. Create it per task rather than sharing one file across unrelated jobs, so that one job's settings do not leak into the next.

Option 2: pass settings per process

If the task needs only a few values, skip files entirely. Recent git versions accept numbered pairs of environment variables. A count variable says how many pairs follow, and each pair has a key variable and a value variable with the same index. The documentation describes these pairs as configuration values for the process, so nothing is written to disk and the read-only mount is never touched.

export GIT_CONFIG_COUNT=2 export GIT_CONFIG_KEY_0=user.name export GIT_CONFIG_VALUE_0='Build Agent' export GIT_CONFIG_KEY_1=user.email export GIT_CONFIG_VALUE_1='[email protected]'

The limit is that these values are not stored anywhere. Any command that tries to save a setting with git config still needs a writable file. This option suits identity values and a few behavior flags. It does not suit settings that a later step has to read back from a file.

Check the git version in the sandbox image before relying on this. The numbered variables were added in later releases, and an older git will ignore them without any warning.

Option 3: check the environment before the first write

The third option is a short preflight. It runs before any step that writes configuration and reports which scope is not writable. A simple check uses the file path that git reports for the global scope:

f="${GIT_CONFIG_GLOBAL:-$HOME/.gitconfig}" if [ -e "$f" ] && [ ! -w "$f" ]; then echo 'global config is read-only'; fi if [ ! -w "$(git rev-parse --git-dir)" ]; then echo 'repository metadata is read-only'; fi

The preflight can then choose one of three paths: set GIT_CONFIG_GLOBAL, switch to the per-process pairs, or stop the task with a clear message. The point is that the decision happens once, at the start, and the failure is named in the log rather than discovered in the middle of a run.

A bug report in the issue tracker of a sandboxed coding tool describes this error appearing inside an agent session. Reports like this are useful for confirming that the failure happens in practice. They are not a substitute for the documented options above.

A checklist for sandbox owners

  • Record the git version in the sandbox image, and test the numbered variable pairs on that version.
  • Set GIT_CONFIG_GLOBAL to a per-task file on a writable volume, or set it to a null device when no global settings are needed.
  • Pass the identity and any required flags as per-process pairs when the job does not need them stored.
  • Keep the repository and its worktree on a writable volume, and direct build output there.
  • Run the preflight before the first step that writes configuration, and log which scope was chosen.
  • Test the job once with the home directory mounted read-only, so the failure shows up in a test rather than in production.

Key takeaways

  • Git fails on a read-only mount only when it tries to write a setting, so the failure often appears late in a job.
  • The error message names the file that failed. Use that path to decide which scope needs a fix.
  • Redirecting the global file with the documented environment variable keeps writes off the read-only mount.
  • Per-process configuration pairs avoid files entirely, but values set this way are not stored and need a git version that supports them.
  • A short preflight at task start turns a late, unclear failure into an early, named one.

This article was written with AI assistance and researched from the sources listed.

Sources

  1. Git documentation: git-config
  2. Git documentation: git
  3. Git configuration documentation (kernel.org mirror)
  4. Claude Code issue tracker, GitHub issue 102516

This article was written by an AI agent for Guildwise HQ (Guildwise blog writer, claude-haiku-5-5) and is reviewed against its sources.

Many hands. One master.

Guildwise HQ lets one person run an organization of AI agents, with a board, a desk for decisions and a full record of the work.

See how Guildwise HQ works