Skip to content
English
  • There are no suggestions because the search field is empty.

IronPdf on Windows: "Attempt to execute managed code after the .NET runtime thread state has been destroyed" on .NET 9.0.5+ and .NET 10

Overview

When running IronPdf on Windows with .NET 9.0.5 or later (including .NET 10), a CLR assertion fires during process exit and returns exit code 127, even though all PDF operations complete successfully. This is a known .NET runtime limitation (not an IronPdf bug) and most commonly breaks CI/CD pipelines that rely on exit codes.

CLR: Assert failure: "Attempt to execute managed code after the .NET runtime thread state has been destroyed."


Affected Environment

  • OS: Windows (FLS is Windows-specific; Linux and macOS are not affected) 
  • .NET versions: .NET 9.0.5+ and .NET 10 (.NET 6, 7, and 8 are not affected)

Solution

  1. Downgrade to .NET 8 (recommended). This is the cleanest fix. .NET 6.0.36 and 8.0.26 have been confirmed to exit cleanly with exit code 0. .NET 9.0.12 and .NET 10.0.6 both trigger the assertion and exit with code 127.
  2. Do not rely on common mitigations — they do not work. The following have all been tested and still trigger the assertion:
    • Installation.SkipShutdown = true
    • SetErrorMode(SEM_NOGPFAULTERRORBOX)
    • Environment.Exit(0)
    • GC.Collect() + WaitForPendingFinalizers()
  3. In CI/CD, use test result files instead of exit codes. Since all IronPdf work completes before the assertion, publishing result files gives an accurate pass/fail signal.
    Azure DevOps: 
       - 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

     

  4. Alternatively, filter the exit code. Wrap the run so the known assertion message does not fail the pipeline:  

    dotnet run 2>stderr.log; EXIT=$?  
    if grep -q "thread state has been destroyed" stderr.log; then exit 0; else exit $EXIT; fi

     

  5. Background (for reference).
    Microsoft added the assertion in .NET 9.0.5 (dotnet/runtime#112809) to prevent Loader Lock deadlocks present in .NET 9.0.0–9.0.4. Thread cleanup now uses FLS (Fiber Local Storage) callbacks, whose OS-controlled ordering can run CLR cleanup before other components finish. Microsoft tracking issue: dotnet/runtime#118741 (milestoned "Future", no planned fix date).