Hmbown/CodeWhale · warning

PTY job is no longer running

Error message

PTY job is no longer running

What it means

After resolving the job, resize_pty polls it and requires ShellStatus::Running before touching the PTY master; a finished (exited, failed, killed, or evicted-pending) job fails with 'PTY job is no longer running'. Resizing a dead pseudo-terminal would either error in portability internals or silently do nothing, so the manager rejects it with this explicit state error.

Solutions

  1. Check job status (via get_output/poll_status or the job list) and skip the resize when it is not Running.
  2. Drop queued resize events when the job reaches a terminal status.
  3. If the job should still be alive, inspect its exit code/stderr to see why it exited before attempting further interaction.
  4. Re-run the command with a fresh task_id if an interactive PTY session is still needed.

Example fix

// before
manager.resize_pty(task_id, size)?;
// after
match manager.poll_status(task_id) {
    Ok(s) if s == ShellStatus::Running => manager.resize_pty(task_id, size)?,
    Ok(s) => eprintln!("job {task_id} is {s:?}; skipping resize"),
    Err(_) => eprintln!("job {task_id} untracked; skipping resize"),
}
Defensive patterns

Strategy: validation

Validate before calling

// Rust: gate the resize on live Running status
let status = manager.poll_status(task_id)?; // errors if untracked
if status == ShellStatus::Running {
    manager.resize_pty(task_id, size)?;
} else {
    eprintln!("job {task_id} is {status:?}; skipping PTY resize");
}

Type guard

fn is_running(status: &ShellStatus) -> bool { *status == ShellStatus::Running }

Try / catch

match manager.resize_pty(task_id, size) {
    Ok(()) => (),
    Err(e) if e.to_string().contains("no longer running") => {
        // read final output/exit code instead of resizing
        let final_out = manager.get_output(task_id, false, 0)?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling resize_pty on a tracked job whose process has already exited — e.g. the job finished between spawn and the resize call, was killed, or the resize event arrives after process exit.

Common situations: Terminal SIGWINCH-style resize delivered just after a long-running command finished; UI resizing a panel bound to a completed PTY job; race between job exit notification and a queued resize request.

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/7d223f47f2540c2a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/shell.rs:2918

        shell.write_stdin_bytes(input, close)?;
        Ok(())
    }

    /// Historical evicted records have no terminal-size evidence.
    pub fn job_terminal_size(&self, task_id: &str) -> Option<PtyDimensions> {
        self.processes
            .get(task_id)
            .and_then(|shell| shell.terminal_size)
    }

    pub fn resize_pty(&mut self, task_id: &str, size: PtyDimensions) -> Result<()> {
        let size = size.validate()?;
        let shell = self
            .processes
            .get_mut(task_id)
            .ok_or_else(|| anyhow!("Job {task_id} not found"))?;
        shell.poll();
        anyhow::ensure!(
            shell.status == ShellStatus::Running,
            "PTY job is no longer running"
        );
        #[cfg(not(target_env = "ohos"))]
        {
            let master = shell.pty_master.as_ref().context("Job is not a PTY")?;
            master
                .resize(PtySize {
                    rows: size.rows,
                    cols: size.cols,
                    pixel_width: 0,
                    pixel_height: 0,
                })
                .context("Failed to resize PTY")?;
            shell.terminal_size = Some(size);
            Ok(())
        }
        #[cfg(target_env = "ohos")]

View on GitHub (pinned to 73e0f67d83)