libnyanpasu/clash-nyanpasu · error · anyhow::Error
Failed to wait for child: {:?}, errs: {}
Error message
Failed to wait for child: {:?}, errs: {} What it means
run_pending_migrations spawns a migration child process, captures its stderr in a background thread, then waits on the child. If child.wait() itself returns an Err (as opposed to a non-zero exit status, which is handled separately), it wraps the io::Error together with all accumulated stderr into this anyhow error.
Source
Thrown at backend/tauri/src/utils/init/mod.rs:90
let mut file = file.lock();
let _ = file.write_all(&buf);
let mut errs = errs_.lock();
errs.push_str(unsafe { std::str::from_utf8_unchecked(&buf) });
}
Err(e) => {
eprintln!("failed to read stderr: {e:?}");
let mut errs = errs_.lock();
errs.push_str(&format!("failed to read stderr: {e:?}\n"));
break;
}
}
}
});
let result = child.wait();
let _l = guard.write(); // Just for waiting the thread read all the output
let err = errs.lock();
result
.map_err(|e| anyhow!("Failed to wait for child: {:?}, errs: {}", e, err))
.and_then(|status| {
if !status.success() {
Err(anyhow!("child process failed: {:?}, err: {}", status, err))
} else {
Ok(())
}
})
}
/// Initialize all the config files
/// before tauri setup
pub fn init_config() -> Result<()> {
// Check if old config dir exist and new config dir is not exist
// let mut old_app_dir: Option<PathBuf> = None;
// let mut app_dir: Option<PathBuf> = None;
// crate::dialog_err!(dirs::old_app_home_dir().map(|_old_app_dir| {
// old_app_dir = Some(_old_app_dir);
// }));View on GitHub (pinned to f7dbce2997)
Solutions
- Inspect the appended `errs` payload in the message for the child's stderr output.
- Re-run the migration; pending migrations are retried on next startup.
- Run the migration binary manually with the same arguments to see the failure directly.
- Check system logs (OOM killer, signals) for why the child was killed.
- Fix the underlying migration script/binary so it exits cleanly.
Example fix
// before
result.map_err(|e| anyhow!("Failed to wait for child: {:?}, errs: {}", e, err))
// after
match result {
Ok(status) if status.success() => Ok(()),
Ok(status) => Err(anyhow!("child process failed: {:?}, err: {}", status, err)),
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => child2.wait().map_err(Into::into),
Err(e) => Err(anyhow!("failed to wait for migration child: {e}; stderr: {err}")),
} Defensive patterns
Strategy: retry
Try / catch
match run_pending_migrations(&bin, &args) {
Ok(()) => {}
Err(e) if e.to_string().contains("Failed to wait for child") => {
log::warn!("migration child lost, retrying: {e}");
retry_migrations()?;
}
Err(e) => return Err(e),
} Prevention
- Keep migration binaries small and fast to reduce OOM/signal risk
- Log child stderr continuously, not only on failure
- Verify migrations run successfully in CI containers matching production
When it happens
Trigger: The migration child could not be waited on: killed by a signal and reaped elsewhere, an OS-level error during wait, or the child handle became invalid (e.g. interrupted wait without retry).
Common situations: Migration binaries being terminated by OOM killers or supervisors during app startup, sandboxed environments restricting child processes, or disk/system failures mid-migration.
Understand the failure class
Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.
Related errors
- child process failed: {:?}, err: {}
- {operation} failed with status {status}
- {stderr}
- cannot repair typed clash config before split_legacy_config
- unrecognized typed config migration state: existing {} is ne
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/fcbd1c6259438dd8.
Report an issue: GitHub.