Managed Code After Thread State Destroyed on .NET 9 and .NET 10
On .NET 9.0.5 and later (including .NET 10), an application using IronPDF exits with a fatal assertion error and exit code 127.
Attempt to execute managed code after the .NET runtime thread state has been destroyed.
Microsoft introduced stricter FLS (Fiber Local Storage) thread cleanup in .NET 9.0.5. The OS controls the ordering of FLS callback execution, which can trigger CLR teardown before IronPDF's Chrome renderer finishes its own shutdown sequence. This issue is Windows-specific; Linux and macOS are not affected. .NET 6, 7, and 8 exit cleanly. IronPDF's PDF generation completes successfully before the assertion fires. Microsoft has filed the issue as "Future" with no planned fix date.
Options
Option 1: Downgrade to .NET 8 (Recommended)
.NET 6.0.36 and .NET 8.0.26 exit cleanly. Downgrade the target framework if clean process exit is required.
Option 2: Use test result files instead of exit codes in CI/CD
The PDF work completes successfully before the assertion fires. Publish test results from result files rather than relying on exit code:
Azure DevOps:
- script: dotnet test --logger "trx" || true
- task: PublishTestResults@2
inputs:
testResultsFiles: '**/*.trx'
- script: dotnet test --logger "trx" || true
- task: PublishTestResults@2
inputs:
testResultsFiles: '**/*.trx'
GitHub Actions:
- run: dotnet test --logger "trx" || true
- uses: dorny/test-reporter@v1
with:
path: '**/*.trx'
reporter: dotnet-trx
- run: dotnet test --logger "trx" || true
- uses: dorny/test-reporter@v1
with:
path: '**/*.trx'
reporter: dotnet-trx
Option 3: Filter exit code in shell
dotnet run 2>stderr.log; EXIT=$?
if grep -q "thread state has been destroyed" stderr.log; then exit 0; else exit $EXIT; fi
dotnet run 2>stderr.log; EXIT=$?
if grep -q "thread state has been destroyed" stderr.log; then exit 0; else exit $EXIT; fi
Known non-working mitigations: Installation.SkipShutdown = true, SetErrorMode, Environment.Exit(0), and manual GC collection before exit.

