Skip to content

feat: Connect backend with CLI#75

Merged
lsoulet merged 2 commits intomainfrom
feat/cli-backend-link
Mar 16, 2026
Merged

feat: Connect backend with CLI#75
lsoulet merged 2 commits intomainfrom
feat/cli-backend-link

Conversation

@lsoulet
Copy link
Copy Markdown
Contributor

@lsoulet lsoulet commented Mar 16, 2026

To test it, build and run backend following README. then run these commands :

Run command with Python : cargo run -p cli -- go --language python --file agent/examples/hello.py
Run command with NodeJS : cargo run -p cli -- go --language node --file agent/examples/hello.js

For status command, type a run command, check the backend logs and copy the job ID.
Then, type this command : cargo run -p cli -- status <JOB_ID>

Summary by CodeRabbit

  • New Features

    • CLI now polls for job completion and displays final status, optional exit code, and stdout/stderr.
  • Bug Fixes

    • Status output simplified to show Job ID and status more clearly.
    • Backend job failures surface clearer HTTP/error details when available.
  • Chores

    • Added vmlinux to backend ignore patterns.
  • Style

    • Various message and formatting refinements across components for clearer logs and errors.

@lsoulet lsoulet self-assigned this Mar 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc5a65ec-9632-4a9d-8620-fb0b7321a866

📥 Commits

Reviewing files that changed from the base of the PR and between adc5aac and c39a20c.

📒 Files selected for processing (7)
  • agent/src/main.rs
  • backend/.gitignore
  • backend/src/initramfs_manager.rs
  • backend/src/main.rs
  • backend/src/vm_lifecycle.rs
  • cli/src/main.rs
  • vmm/src/devices/stdin.rs

📝 Walkthrough
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Connect backend with CLI' directly reflects the main objective of integrating the backend with the CLI as described in the PR summary, which is the primary change across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cli-backend-link
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
cli/src/main.rs (2)

121-121: Unnecessary clone.

run is not used after this line, so you can move run.id directly instead of cloning.

♻️ Proposed fix
-    let job_id = run.id.clone();
+    let job_id = run.id;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/main.rs` at line 121, Replace the unnecessary clone by moving the id
out of run: change the assignment of job_id so it takes ownership of run.id (use
let job_id = run.id;) instead of cloning, and ensure no further uses of run
remain after this line so the move is valid.

152-156: Consider printing stderr content to stderr.

Job stderr output is printed to stdout, which mixes output streams. Using eprintln! would preserve the distinction and allow users to separate stdout from stderr when piping or redirecting CLI output.

♻️ Proposed fix
             if let Some(ref err) = st.stderr {
                 if !err.is_empty() {
-                    println!("{err}");
+                    eprintln!("{err}");
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/main.rs` around lines 152 - 156, The snippet printing job stderr
currently uses println!("{err}") which writes to stdout; change it to write to
stderr by using eprintln! so stderr remains separate — locate the block that
checks st.stderr (the if let Some(ref err) = st.stderr { if !err.is_empty() {
println!("{err}"); } }) and replace the println call with eprintln!("{err}") to
preserve proper output streams.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/main.rs`:
- Around line 127-138: The JSON deserialization chain on the status response
(status_resp.json().await.unwrap_or(...)) is misformatted and failing cargo fmt;
run rustfmt by executing `cd cli && cargo fmt` (or `cargo fmt -- --check` to
verify) to auto-fix the formatting, or manually reformat the chain so the call
to status_resp.json().await.unwrap_or(...) is properly aligned/indented; look
for the symbols status_resp, ErrorBody, client.get and status_url to locate the
block and ensure the return Err(...) line remains unchanged after formatting.
- Around line 123-159: The polling loop in main (the section building status_url
and awaiting status_resp) has no timeout/retry limit; modify it to stop after a
configurable duration or maximum attempts (e.g., add a max_retries or
timeout_secs CLI/config option) and return an error if exceeded. Implement this
by wrapping the polling in a tokio::time::timeout or by counting iterations in
the loop (use a counter like attempts and compare to max_retries), aborting with
a clear error message instead of blocking; update the code that uses
client.get(&status_url).send().await and st: StatusResponse parsing to respect
the timeout/retry logic so any pending json().await calls are not left hanging.

---

Nitpick comments:
In `@cli/src/main.rs`:
- Line 121: Replace the unnecessary clone by moving the id out of run: change
the assignment of job_id so it takes ownership of run.id (use let job_id =
run.id;) instead of cloning, and ensure no further uses of run remain after this
line so the move is valid.
- Around line 152-156: The snippet printing job stderr currently uses
println!("{err}") which writes to stdout; change it to write to stderr by using
eprintln! so stderr remains separate — locate the block that checks st.stderr
(the if let Some(ref err) = st.stderr { if !err.is_empty() { println!("{err}");
} }) and replace the println call with eprintln!("{err}") to preserve proper
output streams.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a88bc6a4-1332-4236-a03c-966e1bdb41be

📥 Commits

Reviewing files that changed from the base of the PR and between d60dd98 and 1592874.

📒 Files selected for processing (2)
  • backend/.gitignore
  • cli/src/main.rs

Comment on lines +123 to +159
loop {
tokio::time::sleep(Duration::from_secs(1)).await;

let status_url = format!("{backend}/status/{job_id}");
let status_resp = client.get(&status_url).send().await?;

if !status_resp.status().is_success() {
let status = status_resp.status();
let err: ErrorBody = status_resp
.json()
.await
.unwrap_or(ErrorBody {
error: format!("HTTP {status}"),
});
return Err(format!("Backend error (HTTP {status}): {}", err.error).into());
}

let st: StatusResponse = status_resp.json().await?;

if st.status == "done" || st.status == "error" {
println!("Status: {}", st.status);
if let Some(code) = st.exit_code {
println!("Exit code: {code}");
}
if let Some(ref out) = st.stdout {
if !out.is_empty() {
println!("{out}");
}
}
if let Some(ref err) = st.stderr {
if !err.is_empty() {
println!("{err}");
}
}
return Ok(());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a timeout or maximum retry limit to prevent indefinite blocking.

The polling loop has no upper bound. If the backend becomes unresponsive, hangs, or a job gets stuck in a non-terminal state, the CLI will block indefinitely. Consider adding a configurable timeout or maximum number of retries.

🛡️ Proposed fix to add a timeout
+    const MAX_POLL_DURATION: Duration = Duration::from_secs(300); // 5 minutes
+    let poll_start = std::time::Instant::now();
+
     loop {
         tokio::time::sleep(Duration::from_secs(1)).await;
 
+        if poll_start.elapsed() > MAX_POLL_DURATION {
+            return Err("Timeout waiting for job to complete".into());
+        }
+
         let status_url = format!("{backend}/status/{job_id}");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
let status_url = format!("{backend}/status/{job_id}");
let status_resp = client.get(&status_url).send().await?;
if !status_resp.status().is_success() {
let status = status_resp.status();
let err: ErrorBody = status_resp
.json()
.await
.unwrap_or(ErrorBody {
error: format!("HTTP {status}"),
});
return Err(format!("Backend error (HTTP {status}): {}", err.error).into());
}
let st: StatusResponse = status_resp.json().await?;
if st.status == "done" || st.status == "error" {
println!("Status: {}", st.status);
if let Some(code) = st.exit_code {
println!("Exit code: {code}");
}
if let Some(ref out) = st.stdout {
if !out.is_empty() {
println!("{out}");
}
}
if let Some(ref err) = st.stderr {
if !err.is_empty() {
println!("{err}");
}
}
return Ok(());
}
}
const MAX_POLL_DURATION: Duration = Duration::from_secs(300); // 5 minutes
let poll_start = std::time::Instant::now();
loop {
tokio::time::sleep(Duration::from_secs(1)).await;
if poll_start.elapsed() > MAX_POLL_DURATION {
return Err("Timeout waiting for job to complete".into());
}
let status_url = format!("{backend}/status/{job_id}");
let status_resp = client.get(&status_url).send().await?;
if !status_resp.status().is_success() {
let status = status_resp.status();
let err: ErrorBody = status_resp
.json()
.await
.unwrap_or(ErrorBody {
error: format!("HTTP {status}"),
});
return Err(format!("Backend error (HTTP {status}): {}", err.error).into());
}
let st: StatusResponse = status_resp.json().await?;
if st.status == "done" || st.status == "error" {
println!("Status: {}", st.status);
if let Some(code) = st.exit_code {
println!("Exit code: {code}");
}
if let Some(ref out) = st.stdout {
if !out.is_empty() {
println!("{out}");
}
}
if let Some(ref err) = st.stderr {
if !err.is_empty() {
println!("{err}");
}
}
return Ok(());
}
}
🧰 Tools
🪛 GitHub Actions: Check source code

[error] 128-133: cargo fmt --check failed: code formatting issue detected in cli/src/main.rs (lines 128-133). Ensure correct formatting of the JSON deserialization and awaiting expressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/src/main.rs` around lines 123 - 159, The polling loop in main (the
section building status_url and awaiting status_resp) has no timeout/retry
limit; modify it to stop after a configurable duration or maximum attempts
(e.g., add a max_retries or timeout_secs CLI/config option) and return an error
if exceeded. Implement this by wrapping the polling in a tokio::time::timeout or
by counting iterations in the loop (use a counter like attempts and compare to
max_retries), aborting with a clear error message instead of blocking; update
the code that uses client.get(&status_url).send().await and st: StatusResponse
parsing to respect the timeout/retry logic so any pending json().await calls are
not left hanging.

@lsoulet lsoulet force-pushed the feat/cli-backend-link branch from adc5aac to 578eaaf Compare March 16, 2026 21:38
@lsoulet lsoulet merged commit dd82621 into main Mar 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants