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
- Ensure every Init has a matching Dispose/Shutdown: use the using-pattern (using var app = Application.Create().Init();) so Dispose always runs.
- For the legacy static model, call Application.Shutdown() before the next Application.Init().
- In tests, call ApplicationImpl.ResetStateStatic() or the provided test reset helper between cases.
- 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
- Always pair Init with Dispose/Shutdown; prefer the using-block pattern.
- Use Application.Create().Init() chaining so each session is a fresh instance.
- In tests, reset state with ApplicationImpl.ResetStateStatic() between cases.
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
- DefaultKeyBindings dictionary is null. Initialize it before
- Cannot use legacy static Application model (Application.Init
- Cannot use modern instance-based model (Application.Create)
- Invoke
- Init must be called before Run.
AI-assisted analysis of tui-cs/Terminal.Gui@2e47b11478 (2026-08-13).
Data as JSON: /api/errors/3d89234de89a90db.
Report an issue: GitHub.