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

  1. Inspect the appended `errs` payload in the message for the child's stderr output.
  2. Re-run the migration; pending migrations are retried on next startup.
  3. Run the migration binary manually with the same arguments to see the failure directly.
  4. Check system logs (OOM killer, signals) for why the child was killed.
  5. 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

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


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/fcbd1c6259438dd8. Report an issue: GitHub.