You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Running from Native AOT app fails to build signature #2187
This is not a missing native library / load failure, and it is not the same as #2160 (static linking for single-file AOT). The native git2-* binary loads correctly next to the published AOT executable.
The failure is in config discovery under Native AOT.
What fails
Configuration.BuildSignature only reads user.name / user.email from the layered config:
Under CoreCLR (dotnet run), those finds succeed and global config is loaded (including include.path chains that often hold user.name / user.email).
Under Native AOT (dotnet publish + run the native exe), the same APIs appear to fail: HasConfig(Global) / HasConfig(System) are false, so only local.git/config is present. If identity is not set locally, BuildSignature returns null.
Why it fails under AOT
GitBuf is a sequential-layout class passed by value to P/Invoke:
On CoreCLR, blittable sequential classes are typically pinned and native code writes directly into the object fields.
On Native AOT, sequential class marshalling is effectively [In]-only: native code can fill a temporary buffer and return success, but managed ptr / size are never updated.
ConvertPath then does:
intresult=pathRetriever(buf);// often 0 (success) under AOT// ...returnLaxFilePathMarshaler.FromNative(buf.ptr);// ptr still IntPtr.Zero
So discovery returns "no path", global/system config is never added, and signature building fails whenever identity lives outside the local repo config.
Minimal probe (struct vs class vs pin)
Calling the same git_config_find_global export after git_libgit2_init:
Call style
CoreCLR
Native AOT
ref blittable struct
OK (rc=0, path filled)
OK
sequential-layout class (current style)
OK
rc=0 but ptr=0
pinned class (GCHandle + IntPtr)
OK
OK
That isolates the bug to marshalling of sequential classes under Native AOT, not to libgit2 path logic or environment variables (USERPROFILE / HOME are fine; setting HOME does not help).
skips git_config_find_* and still loads includes. That confirms libgit2 config parsing is fine; only discovery via GitBuf class P/Invoke is broken under AOT.
Proposed fix
Introduce a blittable buffer type and use it for path-returning APIs that currently go through ConvertPath.
Method-group binding stays clean, e.g. ConvertPath(NativeMethods.git_config_find_global).
4. Expected outcome
After this change, Native AOT published apps should again:
resolve global/system config paths
honor include.path
make repo.Config.BuildSignature(DateTimeOffset.Now) match dotnet run behavior when user.name / user.email come from global config
Follow-up (optional, larger)
Many other APIs still pass class GitBuf for out-buffers (short ids, messages, opts getters, etc.). Those can misbehave under AOT the same way. Migrating remaining out-git_buf call sites to ref GitBufNative would make the whole surface AOT-safe; the config-find path above is the high-impact fix for this issue.
No change required in consumer apps once the library fix ships; workarounds like explicit Configuration.BuildFrom(...) paths are only needed on current NuGet packages.
Happy to open a PR with the surgical GitBufNative + ConvertPath change if that approach looks good to maintainers.
I promise the above is not just AI slop. I have this issue currently and am looking for a proper fix. I'll submit a PR that does so, but it's written by Grok 4.5.
Activity
kzu commentedon Jul 22, 2026
Root cause analysis
This is not a missing native library / load failure, and it is not the same as #2160 (static linking for single-file AOT). The native
git2-*binary loads correctly next to the published AOT executable.The failure is in config discovery under Native AOT.
What fails
Configuration.BuildSignatureonly readsuser.name/user.emailfrom the layered config:When opening a repository, global/system/xdg/programdata paths are resolved via:
Under CoreCLR (
dotnet run), those finds succeed and global config is loaded (includinginclude.pathchains that often holduser.name/user.email).Under Native AOT (
dotnet publish+ run the native exe), the same APIs appear to fail:HasConfig(Global)/HasConfig(System)are false, so only local.git/configis present. If identity is not set locally,BuildSignaturereturnsnull.Why it fails under AOT
GitBufis a sequential-layout class passed by value to P/Invoke:On CoreCLR, blittable sequential classes are typically pinned and native code writes directly into the object fields.
On Native AOT, sequential class marshalling is effectively
[In]-only: native code can fill a temporary buffer and return success, but managedptr/sizeare never updated.ConvertPaththen does:So discovery returns "no path", global/system config is never added, and signature building fails whenever identity lives outside the local repo config.
Minimal probe (struct vs class vs pin)
Calling the same
git_config_find_globalexport aftergit_libgit2_init:refblittable structrc=0, path filled)rc=0butptr=0GCHandle+IntPtr)That isolates the bug to marshalling of sequential classes under Native AOT, not to libgit2 path logic or environment variables (
USERPROFILE/HOMEare fine; settingHOMEdoes not help).Why "explicit path" workarounds work
skips
git_config_find_*and still loads includes. That confirms libgit2 config parsing is fine; only discovery viaGitBufclass P/Invoke is broken under AOT.Proposed fix
Introduce a blittable buffer type and use it for path-returning APIs that currently go through
ConvertPath.1. Add a native-friendly buffer struct
Keep the existing
GitBufclass for call sites that still use class marshalling (can migrate later).2. Change path-find P/Invokes to
ref GitBufNativeAt minimum (these feed
Configuration/ repository discovery):git_config_find_globalgit_config_find_systemgit_config_find_xdggit_config_find_programdatagit_repository_discovergit_buf_dispose(ref GitBufNative)overload3. Update
Proxy.ConvertPathMethod-group binding stays clean, e.g.
ConvertPath(NativeMethods.git_config_find_global).4. Expected outcome
After this change, Native AOT published apps should again:
include.pathrepo.Config.BuildSignature(DateTimeOffset.Now)matchdotnet runbehavior whenuser.name/user.emailcome from global configFollow-up (optional, larger)
Many other APIs still pass class
GitBuffor out-buffers (short ids, messages, opts getters, etc.). Those can misbehave under AOT the same way. Migrating remaining out-git_bufcall sites toref GitBufNativewould make the whole surface AOT-safe; the config-find path above is the high-impact fix for this issue.Not in scope for this fix
libgit2, not about config discovery.Configuration.BuildFrom(...)paths are only needed on current NuGet packages.Happy to open a PR with the surgical
GitBufNative+ConvertPathchange if that approach looks good to maintainers.kzu commentedon Jul 22, 2026
I promise the above is not just AI slop. I have this issue currently and am looking for a proper fix. I'll submit a PR that does so, but it's written by Grok 4.5.
Fix rebase issue when run with dnx go