Read global git config behind symlinked dirs#1278
Merged
Merged
Conversation
go-git's default config loader (xconfig.NewAuto on osfs.Default) reads config files through Go's os.Root, which rejects an absolute symlink in any path component — even one that resolves back inside the root — with "path escapes from parent". Users whose global config lives behind a symlinked directory (e.g. ~/.config managed by a dotfile tool such as chezmoi, GNU Stow, or yadm) therefore had their global config silently dropped: checkpoint-commit author identity fell back to "Unknown", commit signing was skipped, and the push hook printed a "failed to load global git config: path escapes from parent" warning for every cherry-picked commit during a sync/rebase. Register a ConfigLoader plugin backed by osSymlinkFS, a minimal billy.Basic using plain os calls so config reads follow symlinks the way git itself does. The adapter returns raw os errors unwrapped so go-git's os.IsNotExist() fall-through for absent config files keeps working (hence the per-file wrapcheck exclusion). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 61fed68aab4b
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes go-git failing to read global git config when $HOME/.config (or another path component) is an absolute symlink, which previously caused global config to be dropped (e.g., author identity/signing) and produced repeated warnings during checkpoint operations.
Changes:
- Registers a custom go-git
ConfigLoader(via a symlink-followingosSymlinkFS) to load global/system config using plainoscalls. - Adds tests that reproduce the
os.Root“path escapes from parent” failure and validate the fix end-to-end viaGetGitAuthorFromRepo. - Updates lint config/docs to support the new adapter and document the behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/checkpoint/configloader.go | Adds symlink-following ConfigLoader override via osSymlinkFS. |
| cmd/entire/cli/checkpoint/configloader_test.go | Repro + end-to-end tests for symlinked global config behavior. |
| cmd/entire/cli/checkpoint/committed.go | Updates comments to reference the new config loader behavior. |
| CLAUDE.md | Documents the new configloader.go component and rationale. |
| .golangci.yaml | Adjusts lint allowances/exclusions to accommodate the new adapter. |
- Assert the symlink-rejection with errors.Is(err, osfs.ErrPathEscapesParent) instead of substring matching; go-billy wraps the sentinel with %w. - Extract registerConfigLoaderForTest so useAutoConfigLoader and useSymlinkConfigLoader share the reset/register/restore dance; the symlink helper now reuses the production registerSymlinkConfigLoader so test and prod can't drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1dfe9e3ab575
- Rename the Create receiver so it no longer shadows the io/fs package. - Handle os.Unsetenv's error in the test env helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 4025a2a93d51
pjbgf
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
https://entire.io/gh/entireio/cli/trails/434
Problem
A customer reported a flood of warnings during checkpoint push:
Root cause
The customer has
~/.config(or another intermediate path component) as an absolute symlink — standard behavior for dotfile managers (chezmoi, GNU Stow, yadm, …). With no~/.gitconfig, go-git falls back to the XDG path~/.config/git/config.go-git's default config loader (
xconfig.NewAutoonosfs.Default) reads config files through Go'sos.Root, which rejects an absolute symlink in any path component — even one that resolves back inside the root — returningpath escapes from parent. Plainos.Open/os.ReadFilefollow the symlink fine; onlyos.Rootchokes. (Reproduced verbatim, missing leading slash and all.)This wasn't just noise. The global config was silently dropped, so:
Unknown/unknown@localfor checkpoint commits (GetGitAuthorFromRepo→repo.ConfigScoped).commit.gpgsignnever read).Fix
cmd/entire/cli/checkpoint/configloader.goregisters aConfigLoaderplugin backed byosSymlinkFS, a minimalbilly.Basicusing plainoscalls so config reads follow symlinks the way git itself does.plugin.Registerreplaces go-git's default factory during package init (before anyplugin.Get).The adapter returns raw
oserrors unwrapped because go-git's auto loader usesos.IsNotExist(err)(the legacy non-unwrapping check) to fall through absent config files — wrapping would break that. Hence the per-filewrapcheckexclusion, with the reason documented in.golangci.yaml.Tests
TestOSSymlinkFS_ReadsGlobalConfigBehindSymlink— reproduces the exactpath escapes from parentfailure with the default loader and confirmsosSymlinkFSreads through the symlink.TestGetGitAuthorFromRepo_GlobalConfigBehindSymlink— end-to-end: author identity now resolves from a global config behind a symlinked~/.configinstead of falling back toUnknown. (Verified it fails withname = "Unknown"when reverted to the default loader.)Full suite green (5210 tests), lint clean.
Upstream
This is fundamentally a go-git/go-billy limitation (the
autoloader reading trusted host config throughos.Root, diverging from git's symlink-following behavior). A separate upstream conversation/PR will pursue the durable fix; this workaround is forward-compatible and can be dropped once upstream lands.Draft notes / open questions
config.LoadConfig(GlobalScope)fallback atcommitted.go:1913(still usesosfs.Default) untouched, since it's only reached whenConfigScopedreturns empty — which the fix now prevents. Could harden it too.init()-based registry override is slightly implicit; open to moving it to an explicit call in root setup if preferred.🤖 Generated with Claude Code
Note
Low Risk
Localized checkpoint package init and filesystem adapter for config reads; behavior change only affects environments where global config was previously unreadable.
Overview
Fixes global git config not loading when
~/.config(or similar) is an absolute symlink—common with dotfile managers. go-git’s default loader usedos.Rootand failed withpath escapes from parent, sorepo.ConfigScopeddropped global settings: checkpoint commits used Unknown author, GPG signing was skipped, and push/sync logged the same warning once per cherry-picked commit.Adds
configloader.go: at packageinit, registers a go-gitConfigLoaderbacked byosSymlinkFS(billy.Basicover plainos.Open/os.Stat) so config paths follow symlinks like git.GetGitAuthorFromRepocomments point at this loader. Lint: allowbilly.Fileinireturn,wrapcheckoff forconfigloader.go(rawoserrors required foros.IsNotExistfall-through). Tests reproduce the default-loader failure and assert author resolution end-to-end (non-Windows).Reviewed by Cursor Bugbot for commit 4b31870. Configure here.