Hmbown/CodeWhale · error

terminal host window owner changed

Error message

terminal host window owner changed

What it means

WindowControl::hwnd validates that the cached window handle still belongs to the same owning process recorded at capture time. GetWindowThreadProcessId returning a different (or zero) pid means the original host window was destroyed and possibly replaced, so the cached handle is invalid and this error is raised.

Solutions

  1. Re-run capture to bind to the new host window and update the cached handle/owner pid, then retry the operation.
  2. Check that the terminal that started the TUI is still the active window; restart the TUI in the new terminal if not.
  3. Guard toggle flows so they re-validate the host (capture) if hwnd reports an owner change.
Defensive patterns

Strategy: try-catch

Try / catch

match host.hwnd() {
    Err(e) if e.to_string().contains("owner changed") => { state.host = Some(WindowControl::capture()?); retry(); }
    other => other?,
}

Prevention

When it happens

Trigger: Calling hwnd (e.g. from observe during toggle_pin or wait_for) when the current owner pid of the cached HWND differs from self.owner_pid — the terminal host process restarted or the window was destroyed.

Common situations: Windows Terminal/conhost restarted while the TUI ran; the user closed and reopened the terminal; a shell integration reset the console window between capture and a later toggle.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/9653e7d219bb5f15. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/window_control.rs:122

                GetWindowThreadProcessId(hwnd, Some(&mut owner_pid));
            }
            if owner_pid == 0 {
                bail!("terminal host window no longer exists");
            }
            Ok(Self {
                handle: hwnd.0 as isize,
                owner_pid,
            })
        }

        fn hwnd(self) -> Result<HWND> {
            let hwnd = HWND(self.handle as *mut _);
            let mut owner_pid = 0;
            unsafe {
                GetWindowThreadProcessId(hwnd, Some(&mut owner_pid));
            }
            if owner_pid != self.owner_pid {
                bail!("terminal host window owner changed");
            }
            Ok(hwnd)
        }
    }

    #[derive(Clone, Copy)]
    struct Observation {
        rect: RECT,
        topmost: bool,
        maximized: bool,
    }

    impl Observation {
        fn matches(self, target: Self) -> bool {
            self.topmost == target.topmost
                && self.maximized == target.maximized
                && (target.maximized || self.rect == target.rect)
        }

View on GitHub (pinned to 73e0f67d83)