Fix: capture all script output + always log exit code
Two silent failure modes: 1. lines() drops any output chunk not terminated with \n — a script that crashes mid-line (or any final output without a newline) was silently swallowed. Switched to raw 4KB chunk reads which stream incrementally and capture everything. 2. A non-zero exit with no output (e.g. bash exit 127 'command not found') left the log completely empty. Now always appends '[hiy] exit code: N' after the process finishes so there is always at least one diagnostic line regardless of script output. Exit code lookup: exit code: 0 -> success exit code: 1 -> script hit 'set -e' on a failing command exit code: 127 -> bash could not find the script or a command in it exit code: 126 -> script found but not executable (chmod +x missing) exit code: signal -> process killed by OS signal https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
This commit is contained in:
parent
c3f300e8ad
commit
e1a01173ed
1 changed files with 27 additions and 8 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncReadExt, BufReader};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -136,28 +136,47 @@ async fn run_build(state: &AppState, deploy_id: &str) -> anyhow::Result<()> {
|
||||||
let stdout = child.stdout.take().expect("piped stdout");
|
let stdout = child.stdout.take().expect("piped stdout");
|
||||||
let stderr = child.stderr.take().expect("piped stderr");
|
let stderr = child.stderr.take().expect("piped stderr");
|
||||||
|
|
||||||
// Stream stdout and stderr concurrently into the deploy log.
|
// Read stdout/stderr in 4 KB chunks so we stream incrementally AND capture
|
||||||
|
// any partial last line that has no trailing newline (which lines() drops).
|
||||||
let db1 = state.db.clone();
|
let db1 = state.db.clone();
|
||||||
let id1 = deploy_id.to_string();
|
let id1 = deploy_id.to_string();
|
||||||
let stdout_task = tokio::spawn(async move {
|
let stdout_task = tokio::spawn(async move {
|
||||||
let mut lines = BufReader::new(stdout).lines();
|
let mut reader = BufReader::new(stdout);
|
||||||
while let Ok(Some(line)) = lines.next_line().await {
|
let mut buf = vec![0u8; 4096];
|
||||||
let _ = append_log(&db1, &id1, &format!("{}\n", line)).await;
|
loop {
|
||||||
|
match reader.read(&mut buf).await {
|
||||||
|
Ok(0) | Err(_) => break,
|
||||||
|
Ok(n) => {
|
||||||
|
let chunk = String::from_utf8_lossy(&buf[..n]).into_owned();
|
||||||
|
let _ = append_log(&db1, &id1, &chunk).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let db2 = state.db.clone();
|
let db2 = state.db.clone();
|
||||||
let id2 = deploy_id.to_string();
|
let id2 = deploy_id.to_string();
|
||||||
let stderr_task = tokio::spawn(async move {
|
let stderr_task = tokio::spawn(async move {
|
||||||
let mut lines = BufReader::new(stderr).lines();
|
let mut reader = BufReader::new(stderr);
|
||||||
while let Ok(Some(line)) = lines.next_line().await {
|
let mut buf = vec![0u8; 4096];
|
||||||
let _ = append_log(&db2, &id2, &format!("{}\n", line)).await;
|
loop {
|
||||||
|
match reader.read(&mut buf).await {
|
||||||
|
Ok(0) | Err(_) => break,
|
||||||
|
Ok(n) => {
|
||||||
|
let chunk = String::from_utf8_lossy(&buf[..n]).into_owned();
|
||||||
|
let _ = append_log(&db2, &id2, &chunk).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let exit_status = child.wait().await?;
|
let exit_status = child.wait().await?;
|
||||||
let _ = tokio::join!(stdout_task, stderr_task);
|
let _ = tokio::join!(stdout_task, stderr_task);
|
||||||
|
|
||||||
|
// Always record the exit code — the one line that survives even silent failures.
|
||||||
|
let code = exit_status.code().map(|c| c.to_string()).unwrap_or_else(|| "signal".into());
|
||||||
|
append_log(&state.db, deploy_id, &format!("\n[hiy] exit code: {}\n", code)).await?;
|
||||||
|
|
||||||
let final_status = if exit_status.success() { "success" } else { "failed" };
|
let final_status = if exit_status.success() { "success" } else { "failed" };
|
||||||
let finished = Utc::now().to_rfc3339();
|
let finished = Utc::now().to_rfc3339();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue