tui-cs/Terminal.Gui · critical · InvalidOperationException

Init called multiple times without Shutdown

Error message

Init called multiple times without Shutdown

What it means

Thrown by ApplicationImpl.Init when Initialized is already true, meaning the application was already initialized and never shut down/disposed. Terminal.Gui enforces a single Init per application instance; re-entering Init would corrupt driver state, the session stack, and synchronization context. Call Dispose (or the legacy Shutdown) before starting a fresh session, or create a new application instance with Application.Create().

Source

Thrown at Terminal.Gui/App/ApplicationImpl.Lifecycle.cs:29

    /// <inheritdoc/>
    public int? MainThreadId { get; set; }

    /// <inheritdoc/>
    public bool Initialized { get; set; }

    internal SynchronizationContext? SynchronizationContext { get; private set; }

    /// <inheritdoc/>
    public event EventHandler<EventArgs<bool>>? InitializedChanged;

    /// <inheritdoc/>
    public IApplication Init (string? driverName = null)
    {
        if (Initialized)
        {
            Logging.Error ("Init called multiple times without shutdown, aborting.");

            throw new InvalidOperationException ("Init called multiple times without Shutdown");
        }

        MainThreadId = Thread.CurrentThread.ManagedThreadId;

        Trace.Lifecycle (MainThreadId?.ToString (), "Init", $"driverName: {driverName}");

        // In Application.Init(), before starting the input thread:
        // Pre-warm the Wcwidth static cache to prevent ZeroTable._lookup race condition
        // See: https://github.com/spectreconsole/wcwidth/issues/11
        _ = UnicodeCalculator.GetWidth (new Rune ('A'));

        // Thread-safe fence check: Ensure we're not mixing application models
        // Use lock to make check-and-set atomic
        lock (_modelUsageLock)
        {
            // If this is a legacy static instance and instance-based model was used, throw
            if (this == _instance && ModelUsage == ApplicationModelUsage.InstanceBased)
            {

View on GitHub (pinned to 2e47b11478)

Solutions

  1. Ensure every Init has a matching Dispose/Shutdown: use the using-pattern (using var app = Application.Create().Init();) so Dispose always runs.
  2. For the legacy static model, call Application.Shutdown() before the next Application.Init().
  3. In tests, call ApplicationImpl.ResetStateStatic() or the provided test reset helper between cases.
  4. Create a new instance via Application.Create() for each independent session instead of re-initializing the same one.

Example fix

// before
var app = Application.Create ().Init ();
// ... later, same process ...
app.Init (); // throws

// after
using var app = Application.Create ().Init ();
app.Run<MyWindow> ();
// Dispose runs automatically; create a new instance for the next session.
Defensive patterns

Strategy: validation

Validate before calling

if (app.Initialized)
{
    // already initialized; do not call Init again
    return;
}
app.Init ();

Type guard

static bool IsAppReady (IApplication app) => app.Initialized;

Prevention

When it happens

Trigger: Calling app.Init() twice on the same IApplication; calling Application.Init() (legacy static) twice without Application.Shutdown(); reusing a static instance across test cases without reset; a using-block dispose that was skipped due to an earlier exception.

Common situations: Long-running daemon that tries to restart the TUI in-place; test harnesses that share state between tests; IDE hot-reload scenarios that re-run Main; mixing the legacy static model with code that assumes fresh state.

Related errors


AI-assisted analysis of tui-cs/Terminal.Gui@2e47b11478 (2026-08-13). Data as JSON: /api/errors/3d89234de89a90db. Report an issue: GitHub.