Skip to content

feat(minimald): initial, shitty impl of task execution - #338

Merged
twitchyliquid64 merged 1 commit into
mainfrom
tom/update
Jun 4, 2026
Merged

feat(minimald): initial, shitty impl of task execution#338
twitchyliquid64 merged 1 commit into
mainfrom
tom/update

Conversation

@twitchyliquid64

@twitchyliquid64 twitchyliquid64 commented Jun 4, 2026

Copy link
Copy Markdown
Member

Demo:

xxx@lptp:~$ MINIMAL_SESSION_ID=019e841d-b35e-7971-9ef5-a4484540441a \
   scp -o SendEnv=MINIMAL_SESSION_ID -o ProxyCommand='socat - UNIX-CONNECT:/home/xxx/.local/state/minimal/providers/local-0/ssh.sock' \
   -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -r \
   minimal/.minimal xxx@local:
Warning: Permanently added 'local' (ED25519) to the list of known hosts.
minimal.toml                                          100% 1391     6.1MB/s   00:00
profile.ncl                                           100%  322     2.2MB/s   00:00
profile.ncl                                           100%  339     2.3MB/s   00:00

xxx@lptp:~$ MINIMAL_SESSION_ID=019e841d-b35e-7971-9ef5-a4484540441a \
   ssh -o SendEnv=MINIMAL_SESSION_ID -o ProxyCommand='socat - UNIX-CONNECT:/home/xxx/.local/state/minimal/providers/local-0/ssh.sock' \
   -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' local \
   min run startup-time
Warning: Permanently added 'local' (ED25519) to the list of known hosts.
Thu Jun  4 03:12:51 AM UTC 2026
xxx@lptp:~$

That was a LOT of wrestling with all the layers. Hakoniwa isnt async so needed to convert into tokio-flavored wrappers for the pipes, and nickel internals have a bunch of Rc<> that does not play well across await's, etc.

This still need work, namely:

  1. Not intercepting exec's
  2. Supporting multiple invocations
  3. Supporting arguments
  4. Sandbox paths are all kinds of messed up

But this is a good stopping/review point.

Summary by CodeRabbit

  • New Features

    • "min run" over SSH now uses a task-backed execution path that streams and runs task invocations reliably.
  • Refactor

    • Session and workspace handling now persist a cache directory for improved context creation.
    • Safer spawning and termination of remote processes with more consistent stdin/EOF and exit-status behavior.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Sessions and Manager now persist minimal state+cache dirs and can build an mctx::Context on demand; exec handling changed to an Exec -> Stream model, adds a hakoniwa-backed TaskExec/HakoniwaProcess and routing for "min run" commands; CI frees tool cache and expands removed packages.

Changes

Task Execution over SSH with Hakoniwa Backend

Layer / File(s) Summary
Session infrastructure for task context
crates/minimald/src/session.rs, crates/minimald/src/sessions.rs, crates/minimald/src/server.rs
Session actor stores minimal_state_dir and minimal_cache_dir, Session::run accepts both dirs, SessionMessage::MakeContext builds an mctx::Config and workspace dir, SessionHandle::context() requests it, and Manager/ServerState persist and pass both dirs into session startup.
Exec trait and module imports
crates/minimald/src/exec.rs
Introduce Exec trait returning a boxed Stream<Item = io::Result<Process>>; update imports to support stream-based design.
TaskExec, HakoniwaProcess, and producer logic
crates/minimald/src/exec.rs
Add TaskExec and task producer that builds task context from Session and spawns invocations via hakoniwa; implement HakoniwaProcess with cancel-safe wait and SIGKILL termination.
ExecTask wiring and bridge updates
crates/minimald/src/exec.rs
ExecTask now calls exec(session) to obtain a process stream; stdio bridge consumes a Stream of processes sequentially, updates stdin EOF handling and exit-status translation, and includes updated tests and mock helpers.
mctx task mapping refactor
crates/mctx/src/env.rs
Env::task_invocations computes a local mapped_task by evaluating Nickel strings via a VarCtx built from parsed/base args and passes &mapped_task into op::TaskEnv.
Workspace dependencies added
crates/minimald/Cargo.toml
Add workspace dependencies required by the new backend and runtime: futures, hakoniwa, libc, args, graph, mctx, mfile.

CI Workflow

Layer / File(s) Summary
Free Disk Space step update
.github/workflows/ci.yml
Set remove_tool_cache: true and expand remove_packages entries to include additional tooling/package patterns.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • gominimal/minimal#281: Earlier changes to SSH exec scaffolding and bridge logic in crates/minimald/src/exec.rs; this PR extends that scaffolding with session-aware spawning and a hakoniwa task backend.

Suggested reviewers

  • evanspearman
  • 0chroma

Poem

🐰 I hopped through sessions and built a new plan,
I nudged hakoniwa, "Spawn where you can!"
Streams now carry processes, one by one,
Contexts wake up, the tasks are begun.
🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title refers to a real and central part of the changeset (task execution in minimald), but uses informal/unprofessional language ('shitty') that is inappropriate for a commit history and lacks clarity about what specifically was implemented. Consider rephrasing to be more professional and descriptive, e.g., 'feat(minimald): add initial task execution support' or similar, while removing colloquial terms.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald/src/exec.rs`:
- Around line 195-233: The current start_kill() unconditionally sends
libc::kill(self.pid, SIGKILL), which can race with the spawned blocking task
created in wait() (spawn_blocking(child.wait())) and risk killing an unrelated
PID after the child was reaped; change start_kill() to first check whether the
wait has been started (self.wait_handle.is_some()) and, if so, do not send the
raw PID kill (instead return Ok(()) or a benign error), otherwise proceed to
call libc::kill(self.pid, libc::SIGKILL); update start_kill() to reference
wait_handle, pid, and the blocking wait created via spawn_blocking(child.wait())
so we avoid killing by stale PID after wait() has taken ownership of the Child.
- Around line 549-558: The current argv.strip_prefix("min run ") unconditionally
treats the entire suffix as the task name (used to build ExecTask/TaskSpawn)
which wrongly intercepts multi-token commands; instead, parse argv with
split_whitespace and only match the exact three-token form
["min","run","<task>"]. Change the condition around argv.strip_prefix("min run
") to tokenize argv (e.g., argv.split_whitespace().collect::<Vec<_>>()), check
tokens.len() == 3 && tokens[0] == "min" && tokens[1] == "run", and then set
TaskSpawn.task = tokens[2].to_string(); otherwise keep the existing behavior
that falls back to the TokioSpawn shell backend (do not construct
ExecTask/TaskSpawn).
- Around line 114-133: The code calls env.task_invocations(...) and then
unconditionally drops the interactive flag and indexes invs[0], which panics on
zero invocations and mishandles interactive tasks; update the logic that calls
task_invocations to inspect the returned (interactive, invs) tuple: if
interactive is true, return the same rejection as run_task does; if invs.len()
== 0 return an io::Error indicating "no invocations"; if invs.len() > 1 return
the existing error for multiple invocations; only when invs.len() == 1 proceed
to build the container and call env.command(...) using invs[0]; ensure error
messages reference task/container/command construction as currently done.

In `@crates/minimald/src/session.rs`:
- Around line 57-71: The code currently calls
std::fs::create_dir_all(&wsp).unwrap(), which panics on filesystem errors and
kills the session actor; replace that unwrap with error handling that sends the
failure back over the same responder (r) and returns, mirroring the
ConfigBuilder error handling: attempt std::fs::create_dir_all(&wsp), and on
Err(e) send r.send(Err(mctx::Error::from(e).to_string())) (or map the io::Error
into the same error string type used below) and return; keep the existing logic
that builds ConfigBuilder (with_repo_dir/wsp, with_cache_dir, with_state_dir)
untouched otherwise so failures are consistently propagated instead of
panicking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66c07433-c01f-46e3-aa27-266ecacb101f

📥 Commits

Reviewing files that changed from the base of the PR and between 0950234 and 04b4aee.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • crates/mctx/src/env.rs
  • crates/minimald/Cargo.toml
  • crates/minimald/src/exec.rs
  • crates/minimald/src/server.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/sessions.rs

Comment thread crates/minimald/src/exec.rs Outdated
Comment on lines +114 to +133
let (_interactive, invs) = env
.task_invocations(&task, self.args.as_ref())
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;

if invs.len() > 1 {
return Err(std::io::Error::other(
"Lol we don't yet implement tasks properly - just simp ones for now",
));
}

let container = env
.container()
.map_err(|e| std::io::Error::other(format!("building container failed: {}", e)))?;

// TODO: Make this iterate through all the invocations, adapting
// the trait to do so (stream?).
let mut cmd = env
.command(&container, &invs[0].executable, invs[0].args.iter())
.map_err(|e| std::io::Error::other(format!("building command failed: {}", e)))?;

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 | ⚡ Quick win

Validate task_invocations() before using the first entry.

This path drops the interactive flag and Line 132 blindly indexes invs[0]. That gives the wrong behavior for interactive tasks and panics if a task resolves to zero invocations. The existing run_task path in crates/mctx/src/env.rs rejects interactive tasks first; this backend should do the same and handle 0/1/>1 explicitly.

Suggested fix
-        let (_interactive, invs) = env
+        let (interactive, invs) = env
             .task_invocations(&task, self.args.as_ref())
             .await
             .map_err(|e| std::io::Error::other(e.to_string()))?;
 
-        if invs.len() > 1 {
-            return Err(std::io::Error::other(
-                "Lol we don't yet implement tasks properly - just simp ones for now",
-            ));
-        }
+        if interactive {
+            return Err(std::io::Error::other(
+                "interactive tasks are not supported over SSH exec yet",
+            ));
+        }
+
+        let invocation = match invs.as_slice() {
+            [] => return Err(std::io::Error::other("task resolved to no invocations")),
+            [inv] => inv,
+            _ => {
+                return Err(std::io::Error::other(
+                    "multiple invocations are not supported over SSH exec yet",
+                ));
+            }
+        };
 
         let container = env
             .container()
             .map_err(|e| std::io::Error::other(format!("building container failed: {}", e)))?;
 
         // TODO: Make this iterate through all the invocations, adapting
         // the trait to do so (stream?).
         let mut cmd = env
-            .command(&container, &invs[0].executable, invs[0].args.iter())
+            .command(&container, &invocation.executable, invocation.args.iter())
             .map_err(|e| std::io::Error::other(format!("building command failed: {}", e)))?;
📝 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
let (_interactive, invs) = env
.task_invocations(&task, self.args.as_ref())
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
if invs.len() > 1 {
return Err(std::io::Error::other(
"Lol we don't yet implement tasks properly - just simp ones for now",
));
}
let container = env
.container()
.map_err(|e| std::io::Error::other(format!("building container failed: {}", e)))?;
// TODO: Make this iterate through all the invocations, adapting
// the trait to do so (stream?).
let mut cmd = env
.command(&container, &invs[0].executable, invs[0].args.iter())
.map_err(|e| std::io::Error::other(format!("building command failed: {}", e)))?;
let (interactive, invs) = env
.task_invocations(&task, self.args.as_ref())
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
if interactive {
return Err(std::io::Error::other(
"interactive tasks are not supported over SSH exec yet",
));
}
let invocation = match invs.as_slice() {
[] => return Err(std::io::Error::other("task resolved to no invocations")),
[inv] => inv,
_ => {
return Err(std::io::Error::other(
"multiple invocations are not supported over SSH exec yet",
));
}
};
let container = env
.container()
.map_err(|e| std::io::Error::other(format!("building container failed: {}", e)))?;
// TODO: Make this iterate through all the invocations, adapting
// the trait to do so (stream?).
let mut cmd = env
.command(&container, &invocation.executable, invocation.args.iter())
.map_err(|e| std::io::Error::other(format!("building command failed: {}", e)))?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/exec.rs` around lines 114 - 133, The code calls
env.task_invocations(...) and then unconditionally drops the interactive flag
and indexes invs[0], which panics on zero invocations and mishandles interactive
tasks; update the logic that calls task_invocations to inspect the returned
(interactive, invs) tuple: if interactive is true, return the same rejection as
run_task does; if invs.len() == 0 return an io::Error indicating "no
invocations"; if invs.len() > 1 return the existing error for multiple
invocations; only when invs.len() == 1 proceed to build the container and call
env.command(...) using invs[0]; ensure error messages reference
task/container/command construction as currently done.

Comment thread crates/minimald/src/exec.rs Outdated
Comment on lines +195 to +233
if self.wait_handle.is_none() {
let mut child = self
.child
.take()
.expect("HakoniwaProcess::wait invariant: child present until wait succeeds");
self.wait_handle = Some(tokio::task::spawn_blocking(move || child.wait()));
}

// `&mut JoinHandle` is a cancel-safe Future: if the caller drops
// this `wait` future mid-poll the blocking task keeps running,
// and a subsequent call re-borrows the same handle and resumes.
let handle = self.wait_handle.as_mut().expect("set above");
let result = handle.await;
self.wait_handle = None;

match result {
Ok(Ok(status)) => {
self.exit_code = Some(status.exit_code);
Ok(status.exit_code)
}
Ok(Err(e)) => {
self.wait_failed = true;
Err(io::Error::other(e))
}
Err(join_err) => {
self.wait_failed = true;
Err(io::Error::other(join_err))
}
}
}

fn start_kill(&mut self) -> io::Result<()> {
// The `Child` is owned by the blocking-wait task once `wait`
// has been called, so we can't go through `Child::kill`. Send
// SIGKILL directly; ESRCH (process already reaped) is benign.
// SAFETY: `kill(2)` is async-signal-safe and has no Rust-side
// invariants beyond the pid being a valid `pid_t`, which we
// captured from `Child::id()` at construction time.
unsafe { libc::kill(self.pid, libc::SIGKILL) };

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 | 🏗️ Heavy lift

Avoid raw-PID kills after wait() has started.

Once Line 200 moves the child into spawn_blocking(child.wait()), that worker can reap the process before the next select tick. start_kill() still sends SIGKILL to the saved PID unconditionally, so a broken SSH stream can race with PID reuse and kill an unrelated host process.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/exec.rs` around lines 195 - 233, The current start_kill()
unconditionally sends libc::kill(self.pid, SIGKILL), which can race with the
spawned blocking task created in wait() (spawn_blocking(child.wait())) and risk
killing an unrelated PID after the child was reaped; change start_kill() to
first check whether the wait has been started (self.wait_handle.is_some()) and,
if so, do not send the raw PID kill (instead return Ok(()) or a benign error),
otherwise proceed to call libc::kill(self.pid, libc::SIGKILL); update
start_kill() to reference wait_handle, pid, and the blocking wait created via
spawn_blocking(child.wait()) so we avoid killing by stale PID after wait() has
taken ownership of the Child.

Comment thread crates/minimald/src/exec.rs
Comment on lines +57 to +71
let wsp = self.session.workspace_path();
std::fs::create_dir_all(&wsp).unwrap();
let config = match ConfigBuilder::new()
.with_repo_dir(wsp.as_utf8_path())
.with_cache_dir(self.minimal_cache_dir.as_utf8_path())
.with_state_dir(self.minimal_state_dir.as_utf8_path())
.build()
{
Err(e) => {
let _ = r.send(Err(mctx::Error::from(e).to_string()));
return;
}
Ok(c) => c,
};
let _ = r.send(mctx::Context::new(config).map_err(|e| e.to_string()));

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 | ⚡ Quick win

Propagate workspace-init failures instead of panicking the actor.

create_dir_all(&wsp).unwrap() turns a recoverable filesystem error into a session actor crash here. After that, SessionHandle::context() hits its expect("corresponding session is dead") path, so one bad workspace path takes out the whole session.

Suggested fix
             SessionMessage::MakeContext(r) => {
                 let wsp = self.session.workspace_path();
-                std::fs::create_dir_all(&wsp).unwrap();
+                if let Err(e) = std::fs::create_dir_all(&wsp) {
+                    let _ = r.send(Err(e.to_string()));
+                    return;
+                }
                 let config = match ConfigBuilder::new()
                     .with_repo_dir(wsp.as_utf8_path())
                     .with_cache_dir(self.minimal_cache_dir.as_utf8_path())
                     .with_state_dir(self.minimal_state_dir.as_utf8_path())
                     .build()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/session.rs` around lines 57 - 71, The code currently
calls std::fs::create_dir_all(&wsp).unwrap(), which panics on filesystem errors
and kills the session actor; replace that unwrap with error handling that sends
the failure back over the same responder (r) and returns, mirroring the
ConfigBuilder error handling: attempt std::fs::create_dir_all(&wsp), and on
Err(e) send r.send(Err(mctx::Error::from(e).to_string())) (or map the io::Error
into the same error string type used below) and return; keep the existing logic
that builds ConfigBuilder (with_repo_dir/wsp, with_cache_dir, with_state_dir)
untouched otherwise so failures are consistently propagated instead of
panicking.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

40-41: ⚡ Quick win

Make Rust toolchain provisioning explicit after enabling tool-cache cleanup.

With remove_tool_cache: true on Line 40, this job depends on how endersonmenezes/free-disk-space@v3 treats runner toolcache. Since later steps assume cargo/rustup already exist, explicitly installing Rust here would make CI deterministic across runner image changes.

Suggested hardening
       - name: Free Disk Space
         uses: endersonmenezes/free-disk-space@v3 # Use `@main` for latest, `@v3` for stable
         with:
           remove_android: true
           remove_dotnet: true
           remove_haskell: true
           remove_tool_cache: true
           remove_packages: "azure-cli dotnet-sdk-8.0 temurin-* mysql* firefox"
+      - name: Install Rust toolchain
+        uses: dtolnay/rust-toolchain@stable
       - uses: actions/checkout@v6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 40 - 41, Because remove_tool_cache:
true can purge the runner toolcache and CI later assumes cargo/rustup exists,
add an explicit step to provision the Rust toolchain after the cleanup step:
detect the job step that sets remove_tool_cache: true and insert a step that
installs rustup/cargo (e.g., via actions-rs/toolchain or rustup-init) and sets
the desired toolchain (stable or a pinned version) so subsequent steps relying
on cargo/rustup are deterministic; reference the remove_tool_cache: true setting
and the remove_packages listing to place the new installation step immediately
after the cleanup action.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 40-41: Because remove_tool_cache: true can purge the runner
toolcache and CI later assumes cargo/rustup exists, add an explicit step to
provision the Rust toolchain after the cleanup step: detect the job step that
sets remove_tool_cache: true and insert a step that installs rustup/cargo (e.g.,
via actions-rs/toolchain or rustup-init) and sets the desired toolchain (stable
or a pinned version) so subsequent steps relying on cargo/rustup are
deterministic; reference the remove_tool_cache: true setting and the
remove_packages listing to place the new installation step immediately after the
cleanup action.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f019fe6-eeb6-49df-974a-101a0a9bf2b1

📥 Commits

Reviewing files that changed from the base of the PR and between 04b4aee and 9020f96.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • crates/mctx/src/env.rs
  • crates/minimald/Cargo.toml
  • crates/minimald/src/exec.rs
  • crates/minimald/src/server.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/sessions.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/minimald/src/server.rs
  • crates/minimald/src/sessions.rs
  • crates/mctx/src/env.rs
  • crates/minimald/src/exec.rs

Comment thread crates/minimald/src/exec.rs Outdated
Comment on lines +83 to +84
let graph = ctx
.graph_from_all_packages()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any concerns with calling sync functions like graph_from_all_packages from the this async fn?

// SAFETY: `kill(2)` is async-signal-safe and has no Rust-side
// invariants beyond the pid being a valid `pid_t`, which we
// captured from `Child::id()` at construction time.
unsafe { libc::kill(self.pid, libc::SIGKILL) };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any concerns about pid wrapping? (I guess that is what CR is talking about too)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good find! will launch em in a blocking task.

Comment thread crates/minimald/src/exec.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/minimald/src/session.rs`:
- Around line 91-95: The context() method currently panics on a closed session
actor because recv.await uses expect and send errors are ignored; update
Session::context to propagate errors instead: check the result of
self.0.send(SessionMessage::MakeContext(send)).await and return Err("session
actor is dead".into()) if that send fails, then await recv and match its
Result—on Ok(ctx) return Ok(ctx) and on Err(_) return Err("session actor is
dead".into())—so both send and recv failure paths return Err<String> rather than
panicking (referencing the context() function, SessionMessage::MakeContext,
self.0.send, and recv.await).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23e5bf44-d0bd-4e55-87ed-5e43b5be0d99

📥 Commits

Reviewing files that changed from the base of the PR and between 9020f96 and 51e5c0a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • crates/mctx/src/env.rs
  • crates/minimald/Cargo.toml
  • crates/minimald/src/exec.rs
  • crates/minimald/src/server.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/sessions.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/minimald/src/server.rs
  • .github/workflows/ci.yml
  • crates/minimald/Cargo.toml
  • crates/mctx/src/env.rs
  • crates/minimald/src/sessions.rs

Comment on lines +91 to +95
pub async fn context(&self) -> Result<mctx::Context, String> {
let (send, recv) = oneshot::channel();
// Ignore send errors - the recv will also fail.
let _ = self.0.send(SessionMessage::MakeContext(send)).await;
recv.await.expect("corresponding session is dead")

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 | ⚡ Quick win

Return an error when the session actor is gone.

This method advertises Result<_, String>, but a closed actor channel still panics via expect. If the session task dies, callers get an accepted exec that aborts mid-flight instead of a normal error.

Proposed fix
     pub async fn context(&self) -> Result<mctx::Context, String> {
         let (send, recv) = oneshot::channel();
-        // Ignore send errors - the recv will also fail.
-        let _ = self.0.send(SessionMessage::MakeContext(send)).await;
-        recv.await.expect("corresponding session is dead")
+        self.0
+            .send(SessionMessage::MakeContext(send))
+            .await
+            .map_err(|_| "corresponding session is dead".to_string())?;
+        recv.await
+            .unwrap_or_else(|_| Err("corresponding session is dead".to_string()))
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/minimald/src/session.rs` around lines 91 - 95, The context() method
currently panics on a closed session actor because recv.await uses expect and
send errors are ignored; update Session::context to propagate errors instead:
check the result of self.0.send(SessionMessage::MakeContext(send)).await and
return Err("session actor is dead".into()) if that send fails, then await recv
and match its Result—on Ok(ctx) return Ok(ctx) and on Err(_) return Err("session
actor is dead".into())—so both send and recv failure paths return Err<String>
rather than panicking (referencing the context() function,
SessionMessage::MakeContext, self.0.send, and recv.await).

@twitchyliquid64
twitchyliquid64 merged commit a2b6393 into main Jun 4, 2026
37 of 43 checks passed
@twitchyliquid64
twitchyliquid64 deleted the tom/update branch June 4, 2026 20:38
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