Environment
- macOS 26.5.1 (Tahoe), Apple Silicon (arm64)
- Homebrew LDC 1.42.0 (DMD frontend 2.112), dub, DCD 0.16.2
- serve-d v0.8.0-beta.18 (both the published macOS release artifact and a local rebuild — see below)
- Editors affected: any serve-d client; observed via Nova by Panic (D-Velop extension), reproduced outside any editor with a standalone LSP stdio driver
On this combination, D language support via the current release is fully broken, for two independent reasons. I've root-caused both; details and a minimal repro for each below.
Problem 1: the published macOS release binary segfaults at load on macOS 26
The beta.18 macOS artifact crashes before main() — serve-d --version alone is enough:
$ ./serve-d --version
[1] segmentation fault exit code 139
macOS writes an .ips crash report on every launch attempt. Faulting-thread backtrace (parsed from ~/Library/Logs/DiagnosticReports/serve-d-*.ips):
os_version: macOS 26.5.1 (25F80)
exception : EXC_BAD_ACCESS / SIGSEGV / KERN_INVALID_ADDRESS at 0x00006c81faf6a770
Thread 0 crashed:
libsystem_pthread.dylib pthread_getspecific
serve-d +0x6843b0
dyld invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const
dyld mach_o::UnsafeHeader::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const
dyld mach_o::UnsafeHeader::forEachSection(void (mach_o::UnsafeHeader::SectionInfo const&, bool&) block_pointer) const
dyld dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const
dyld dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const
dyld dyld4::JustInTimeLoader::runInitializers(dyld4::RuntimeState&) const
dyld dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, ...) const
dyld dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const
dyld dyld4::prepare(dyld4::APIs&, mach_o::UnsafeHeader const*)
The crash is in druntime's thread-local-storage setup (pthread_getspecific) inside a static initializer, while dyld is still running initializers — i.e., a load-time incompatibility, not a runtime bug:
otool -L shows the binary links only /usr/lib/libSystem.B.dylib → not a missing dependency
codesign -dv shows ad-hoc/linker signing, no quarantine attribute → not Gatekeeper
Confirmation that it's the artifact, not the source: rebuilding the same tag (v0.8.0-beta.18) locally with LDC 1.42 on macOS 26 produces a binary that runs fine:
$ git checkout v0.8.0-beta.18 && dub build --compiler=ldc2 --build=release
$ ./serve-d --version
serve-d standalone v0.8.0-beta.18
Included features: "d", "workspaces"
Built: Sun Jul 5 16:29:17 2026
with compiler LDC v2.112 on osx aarch64
dub, dfmt and dscanner are bundled within (compiled in)
The published artifact (built ~Oct 2024) appears to predate macOS 26's loader/TLS changes. Every macOS 26 user who installs serve-d through an editor extension's auto-download (code-d, D-Velop, etc.) gets a server that crash-loops silently.
Problem 2: stdlib auto-detection cannot parse LDC ≥ 1.42's ldc2.conf
After the local rebuild (Problem 1 solved), completion still fails: "Could not initialize DCD". Root cause is in stdlib path auto-detection (stdlib_detect.d / parseLdcConfImports), and it has two layers:
2a. ldc2.conf is now a directory — fixed in master, but unreleased
Homebrew LDC ≥ 1.42 ships /opt/homebrew/etc/ldc2.conf as a directory of config fragments (*.conf), not a single file. beta.18 tries to read it as a file:
FileException: /opt/homebrew/etc/ldc2.conf: Is a directory
Master already fixes this (commit 41b0edb, "Support directory form of ldc2.conf") by iterating the fragments. 👍 But no tagged release contains it — beta.18 is still the latest.
2b. The config parser doesn't understand the ~= append operator — present in master too
I backported 41b0edb onto beta.18 locally to verify. Directory iteration then works (all five fragments are found), but parsing fatally fails on 30-compiler.conf:
[error] extension.d:993:startDCDServer object.Exception@source/served/utils/stdlib_detect.d(425): Could not read ldc2 config file: /opt/homebrew/etc/ldc2.conf/30-compiler.conf: Error while reading config file: /opt/homebrew/etc/ldc2.conf/30-compiler.conf
line 3: Was expecting token ':' or '='. Got "unknown token" (~) instead.
LDC 1.42's fragments use the ~= (append) operator so drop-in fragments can extend the shared default section:
"default":
{
switches ~= [ "-defaultlib=phobos2-ldc,druntime-ldc" ];
post-switches ~= [ "-I/opt/homebrew/Cellar/ldc/1.42.0/include/dlang/ldc" ];
};
serve-d's bundled ldc2.conf parser only accepts = / :. Two aggravating factors:
30-compiler.conf is exactly the fragment that carries the -I phobos/druntime path, so skipping it loses the one value auto-detection exists to find; and
- the per-fragment parse error is rethrown as fatal, so one unparseable fragment aborts DCD initialization entirely rather than degrading.
Net effect: even with master's directory fix, stdlib auto-detect cannot work against LDC ≥ 1.42.
Reproduction
- Problem 1: macOS 26 (arm64) + the published beta.18 macOS artifact →
./serve-d --version segfaults.
- Problem 2: macOS 26 + Homebrew LDC ≥ 1.42 + a serve-d built from current source → open any dub project in any serve-d client → "Could not initialize DCD". Reproducible headlessly with a small Python LSP stdio driver and
--loglevel all (stderr shows the exception above) — happy to attach the script.
Workaround for affected users
- Build serve-d from source with current LDC (
dub build --compiler=ldc2 --build=release at tag v0.8.0-beta.18) — fixes Problem 1.
- Set an explicit
d.stdlibPath (e.g. ["/opt/homebrew/include/dlang/ldc"]) — bypasses Problem 2 entirely, since auto-detection is skipped.
Suggested fixes / ask
In increasing order of effort:
- Cut a beta.19 release from current master on current macOS CI. This alone fixes Problem 1 for everyone and ships the existing 2a fix.
- Make a single unparseable fragment non-fatal in
parseLdcConfImports (skip + warn instead of rethrow). Small change; turns a hard DCD failure into graceful degradation, and lets an explicit stdlibPath coexist with a partially-parsed config.
- Support the
~= append operator in the ldc2.conf parser. This is the complete fix for LDC ≥ 1.42 auto-detection (the -I path lives in a ~= assignment).
Thanks for serve-d — happy to test branches or provide any further diagnostics from this setup.
Environment
On this combination, D language support via the current release is fully broken, for two independent reasons. I've root-caused both; details and a minimal repro for each below.
Problem 1: the published macOS release binary segfaults at load on macOS 26
The beta.18 macOS artifact crashes before
main()—serve-d --versionalone is enough:macOS writes an
.ipscrash report on every launch attempt. Faulting-thread backtrace (parsed from~/Library/Logs/DiagnosticReports/serve-d-*.ips):The crash is in druntime's thread-local-storage setup (
pthread_getspecific) inside a static initializer, while dyld is still running initializers — i.e., a load-time incompatibility, not a runtime bug:otool -Lshows the binary links only/usr/lib/libSystem.B.dylib→ not a missing dependencycodesign -dvshows ad-hoc/linker signing, no quarantine attribute → not GatekeeperConfirmation that it's the artifact, not the source: rebuilding the same tag (
v0.8.0-beta.18) locally with LDC 1.42 on macOS 26 produces a binary that runs fine:The published artifact (built ~Oct 2024) appears to predate macOS 26's loader/TLS changes. Every macOS 26 user who installs serve-d through an editor extension's auto-download (code-d, D-Velop, etc.) gets a server that crash-loops silently.
Problem 2: stdlib auto-detection cannot parse LDC ≥ 1.42's
ldc2.confAfter the local rebuild (Problem 1 solved), completion still fails: "Could not initialize DCD". Root cause is in stdlib path auto-detection (
stdlib_detect.d/parseLdcConfImports), and it has two layers:2a.
ldc2.confis now a directory — fixed in master, but unreleasedHomebrew LDC ≥ 1.42 ships
/opt/homebrew/etc/ldc2.confas a directory of config fragments (*.conf), not a single file. beta.18 tries to read it as a file:Master already fixes this (commit 41b0edb, "Support directory form of ldc2.conf") by iterating the fragments. 👍 But no tagged release contains it — beta.18 is still the latest.
2b. The config parser doesn't understand the
~=append operator — present in master tooI backported 41b0edb onto beta.18 locally to verify. Directory iteration then works (all five fragments are found), but parsing fatally fails on
30-compiler.conf:LDC 1.42's fragments use the
~=(append) operator so drop-in fragments can extend the shareddefaultsection:serve-d's bundled ldc2.conf parser only accepts
=/:. Two aggravating factors:30-compiler.confis exactly the fragment that carries the-Iphobos/druntime path, so skipping it loses the one value auto-detection exists to find; andNet effect: even with master's directory fix, stdlib auto-detect cannot work against LDC ≥ 1.42.
Reproduction
./serve-d --versionsegfaults.--loglevel all(stderr shows the exception above) — happy to attach the script.Workaround for affected users
dub build --compiler=ldc2 --build=releaseat tag v0.8.0-beta.18) — fixes Problem 1.d.stdlibPath(e.g.["/opt/homebrew/include/dlang/ldc"]) — bypasses Problem 2 entirely, since auto-detection is skipped.Suggested fixes / ask
In increasing order of effort:
parseLdcConfImports(skip + warn instead of rethrow). Small change; turns a hard DCD failure into graceful degradation, and lets an explicitstdlibPathcoexist with a partially-parsed config.~=append operator in the ldc2.conf parser. This is the complete fix for LDC ≥ 1.42 auto-detection (the-Ipath lives in a~=assignment).Thanks for serve-d — happy to test branches or provide any further diagnostics from this setup.