vi/websocat · error
assertion failed 1425
Error message
assertion failed 1425
What it means
This panic comes from .expect("assertion failed 1425") on the child process stdout handle inside ProcessPeer::read. ProcessPeer keeps an Option<Child> whose stdout is only Some when the child was spawned with Stdio::piped(); the expect fires when stdout() returns None. It means the code tried to read from a child whose standard output was never captured or already taken.
Solutions
- When spawning the child, always set .stdout(Stdio::piped()) before creating the ProcessPeer.
- Ensure nothing else calls Child::stdout().take() on the same child before ProcessPeer reads it.
- Check the code that constructs ProcessPeer and make piped stdout a required precondition, failing fast at spawn time with a clear error instead of at read time.
Example fix
// before
let child = Command::new(prog).spawn()?;
let peer = ProcessPeer::new(child);
peer.read(buf)?; // panics: stdout not piped
// after
let child = Command::new(prog)
.stdout(Stdio::piped())
.spawn()?;
let peer = ProcessPeer::new(child);
peer.read(buf)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_piped_stdout(cmd: &mut Command) -> &mut Command {
cmd.stdout(Stdio::piped())
}
// call before spawn and before constructing ProcessPeer Type guard
fn child_stdout_available(child: &Child) -> bool {
// peek without taking: wrap child stdout in a field checked at construction
child.stdout.is_some()
} Try / catch
// panics cannot be caught in Rust (without catch_unwind); validate first
let peer = ProcessPeer::new(child);
match peer.try_read(buf) {
Ok(n) => process(n),
Err(e) => eprintln!("child stdout unavailable: {}", e),
} Prevention
- Always spawn child processes with .stdout(Stdio::piped()) when output will be read.
- Never call Child::stdout().take() outside the owning abstraction.
- Validate stdio configuration right after spawn, before constructing the peer.
- Make piped stdio a constructor requirement of ProcessPeer so misuse fails early.
When it happens
Trigger: Creating a ProcessPeer for a child spawned without .stdout(Stdio::piped()), or after the child's stdout handle was already taken (take() on Child::stdout) and never put back.
Common situations: Configuring Command without piped stdout but assuming the peer wraps output; a spawn-time default (inherit/null) differing between environments or library versions; reading after another component (e.g. a logger) consumed the handle.
Related errors
- Assertion failed 193913
- write zero byte into writer
- lint should have caught the missing pkcs12_der option
- Time went backwards
- Nowhere to connect it
AI-assisted analysis of vi/websocat@3a3574cd2f (2026-09-12).
Data as JSON: /api/errors/f517588f0896232d.
Report an issue: GitHub.
Appendix: source
Thrown at src/process_peer.rs:207
exit_on_disconnect: bool,
}
#[derive(Clone)]
struct ProcessPeer {
chld: Rc<RefCell<ForgetfulProcess>>,
sighup_on_zero: bool,
sighup_on_close: bool,
}
impl Read for ProcessPeer {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.chld
.borrow_mut()
.chld
.as_mut()
.unwrap()
.stdout()
.as_mut()
.expect("assertion failed 1425")
.read(buf)
}
}
impl Write for ProcessPeer {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
#[cfg(unix)]
{
if self.sighup_on_zero && buf.is_empty() {
// TODO use nix crate?
if let Some(ref chld) = self.chld.borrow().chld {
unsafe {
extern crate libc;
libc::kill(chld.id() as libc::pid_t, libc::SIGHUP);
}
}
}
}View on GitHub (pinned to 3a3574cd2f)