crates/minimald: create scaffolding for building up SSH channels - #235
Conversation
📝 WalkthroughWalkthroughRefactors minimald to add per-connection SSH channel state, a JSON-over-SSH one-shot RPC framework (GetVersion), persistent on-disk host-key memoization via shared ServerStateHandle, per-instance daemon paths and UDS sockets, and workspace dependency additions. ChangesSSH Channel Management and RPC Subsystems
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
|
Its not much, but this simple GetVersion RPC works: xxx@lptp:~$ echo "null" | \
ssh -sT -o ProxyCommand='socat - UNIX-CONNECT:/home/xxx/.local/state/minimal/providers/local-0/ssh.sock' \
-o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' local \
minimald-v1-GetVersion
Warning: Permanently added 'local' (ED25519) to the list of known hosts.
{"version":"0.0.1","long_version":"0.0.1 (dirty b20ffb6a)","stdlib_version":"0.0.15"} |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
crates/minimald/src/server.rs (1)
121-127: ⚡ Quick winUse
expect()instead ofunwrap()for clarity.Per coding guidelines, production code should use
expect("reason")instead of bareunwrap()to document why the invariant holds.Proposed fix
let russh_config = Arc::new(russh::server::Config { - keys: vec![self.state.host_key().unwrap()], + keys: vec![self.state.host_key().expect("host key should be loadable or generatable")], auth_rejection_time_initial: Some(std::time::Duration::ZERO),🤖 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/server.rs` around lines 121 - 127, Replace the bare unwrap() used when obtaining the host key inside async fn run()—specifically the call self.state.host_key().unwrap() used to populate the russh_config keys vector—with expect("...") and a short reason; update the call to self.state.host_key().expect("host key must be present when starting SSH server") (or similar) so the code documents the invariant and provides a clear panic message if the key is missing.crates/minimald/src/rpc.rs (1)
7-44: 💤 Low valueRemove or document commented-out code.
Lines 40-41 contain commented-out
flush()andshutdown()calls. Per coding guidelines, commented-out code should not be included. Either remove these lines or add a comment explaining why they're kept (e.g., for future consideration).Proposed fix (removal)
stream .write_all(&response_bytes) .await .map_err(russh::Error::from)?; - //stream.flush().await.map_err(russh::Error::from)?; - //stream.shutdown().await.map_err(russh::Error::from)?; Ok(()) }🤖 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/rpc.rs` around lines 7 - 44, In OneshotSshRpc::handle, remove the two commented-out calls to stream.flush() and stream.shutdown() (the lines containing stream.flush().await.map_err(russh::Error::from)?; and stream.shutdown().await.map_err(russh::Error::from)?;) OR replace them with a short comment explaining why they are intentionally omitted (e.g., the underlying RuChannel stream already closes/flushes when dropped or those operations caused issues), so the repository has no unexplained commented-out code.crates/minimald/src/connection.rs (1)
177-185: 💤 Low valueRemoved channel's
handle_channel_closecall has no effect.The channel is removed from the map via
remove(), thenhandle_channel_close()is called on the removed value. Since the channel is already removed fromself.channels, marking itclosed = trueserves no purpose—the mutatedcis immediately dropped.If
handle_channel_closeis meant to perform cleanup beyond setting the flag, consider doing so before removal, or simply remove the call.Proposed simplification
fn handle_channel_close(&mut self, id: ChannelId) -> Result<(), ConnectionError> { - match self.channels.remove(&id) { - None => tracing::warn!("request to close channel {id} which does not exist"), - Some(mut c) => { - c.handle_channel_close(); - } - }; + if self.channels.remove(&id).is_none() { + tracing::warn!("request to close channel {id} which does not exist"); + } Ok(()) }🤖 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/connection.rs` around lines 177 - 185, The call to Channel::handle_channel_close on the value returned by self.channels.remove(&id) is pointless because the removed channel is dropped immediately; either call handle_channel_close before removal or drop the call entirely. Fix handle_channel_close by locating the function handle_channel_close in connection.rs and change the flow in handle_channel_close(&mut self, id: ChannelId) to first borrow the channel mutably (e.g., self.channels.get_mut(&id) or use Entry API), call c.handle_channel_close() to perform any cleanup, and only then remove the channel (self.channels.remove(&id)); if handle_channel_close only sets an internal closed flag and no external cleanup is required, remove the call instead.
🤖 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/connection.rs`:
- Around line 375-376: Fix the typo in the inline comment near the channel_eof
note: change "propergation" to "propagation" in the comment that reads "// NOTE:
channel_eof left at default impl, implementing channel_eof prevents propergation
to channel handle" (search for the comment mentioning channel_eof to locate it).
- Line 95: The doc comment for the method that "Consumes a pending session,
returning its state and leaving [`ChannelInner::Finished`] in its place"
references a non-existent variant; update the doc to reference the actual enum
variant `ChannelInner::Taken` (or match the enum variant name used in the code)
so the documentation matches the implementation and avoids confusion when
locating the `ChannelInner` variant.
- Around line 351-364: The spawned async task currently uses println! for
logging the result of rpc::GetVersion.handle(...).await; replace println! with a
tracing macro (e.g., tracing::debug! or tracing::info!) so the output respects
structured logging and levels. Concretely, in the spawn async move block around
rpc::GetVersion.handle(c_hnd, |_req| { ... }).await, assign the awaited result
to a local variable (e.g., let resp = ... .await) and then call
tracing::debug!("handle response: {:?}", resp); (ensure tracing is in scope or
fully qualify the macro). This replaces the println! usage and preserves the
same message content.
In `@crates/minimald/src/main.rs`:
- Around line 30-36: The fallback that constructs
PathBuf::from("~/.local/state") (in minimal_state_dir) is incorrect because "~"
is not expanded; change the fallback to use dirs::home_dir() and join the
".local/state" and "minimal" segments (e.g., dirs::home_dir().map(|h|
h.join(".local").join("state")).unwrap_or_else(||
PathBuf::from(".local/state")).join("minimal")), and make the analogous change
for the cache fallback used in minimal_cache_dir (replace
PathBuf::from("~/.local/cache") with building from dirs::home_dir() and joining
".local/cache" with a safe fallback).
---
Nitpick comments:
In `@crates/minimald/src/connection.rs`:
- Around line 177-185: The call to Channel::handle_channel_close on the value
returned by self.channels.remove(&id) is pointless because the removed channel
is dropped immediately; either call handle_channel_close before removal or drop
the call entirely. Fix handle_channel_close by locating the function
handle_channel_close in connection.rs and change the flow in
handle_channel_close(&mut self, id: ChannelId) to first borrow the channel
mutably (e.g., self.channels.get_mut(&id) or use Entry API), call
c.handle_channel_close() to perform any cleanup, and only then remove the
channel (self.channels.remove(&id)); if handle_channel_close only sets an
internal closed flag and no external cleanup is required, remove the call
instead.
In `@crates/minimald/src/rpc.rs`:
- Around line 7-44: In OneshotSshRpc::handle, remove the two commented-out calls
to stream.flush() and stream.shutdown() (the lines containing
stream.flush().await.map_err(russh::Error::from)?; and
stream.shutdown().await.map_err(russh::Error::from)?;) OR replace them with a
short comment explaining why they are intentionally omitted (e.g., the
underlying RuChannel stream already closes/flushes when dropped or those
operations caused issues), so the repository has no unexplained commented-out
code.
In `@crates/minimald/src/server.rs`:
- Around line 121-127: Replace the bare unwrap() used when obtaining the host
key inside async fn run()—specifically the call self.state.host_key().unwrap()
used to populate the russh_config keys vector—with expect("...") and a short
reason; update the call to self.state.host_key().expect("host key must be
present when starting SSH server") (or similar) so the code documents the
invariant and provides a clear panic message if the key is missing.
🪄 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: 38c6f809-00a1-4d98-bf90-741934b270de
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
Cargo.tomlcrates/minimald/Cargo.tomlcrates/minimald/src/connection.rscrates/minimald/src/main.rscrates/minimald/src/rpc.rscrates/minimald/src/server.rs
b20ffb6 to
837b2ca
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
crates/minimald/src/connection.rs (1)
37-37: ⚡ Quick winRemove or justify these
dead_codesuppressions.These
#[allow(dead_code)]attributes are all unexplained. If this is temporary scaffolding, add a brief reason next to each suppression; otherwise prefer narrowing visibility or removing unused surface area.As per coding guidelines, "Justify any
#[allow(...)]attributes with a comment explaining why the lint is suppressed."Also applies to: 47-47, 56-56, 68-68, 120-120, 194-194
🤖 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/connection.rs` at line 37, There are unexplained #[allow(dead_code)] attributes in this module; for each occurrence of #[allow(dead_code)] either remove the attribute and tighten the API (make the item private or delete unused functions/structs), or keep it but add a one-line justification comment immediately next to the attribute (e.g., "// allow(dead_code): exported for benches/tests/feature-gate X" or "// allow(dead_code): used only in FFI/extern tests") — alternatively prefer using #[cfg(test)] or #[cfg(feature = "...")] on the item instead of blanket dead_code suppression; update the occurrences of #[allow(dead_code)] accordingly so every suppression has a clear justification or is eliminated.
🤖 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/connection.rs`:
- Around line 134-139: Change Connection::from_socket to return
Result<(ConnectionHandle, RunningSession<ConnectionHandler>), ConnectionError>
instead of unwrapping run_stream; propagate russh::server::run_stream(...)
errors (map them into ConnectionError with context) and return Err on failure
rather than panicking. For the RPC startup, capture the tokio::task::JoinHandle
returned when calling tokio::spawn for rpc::GetVersion.handle(...).await and
either .await the JoinHandle (and log/map any JoinError or inner
ConnectionError) or spawn a small task that logs the error if it fails, so the
Result<(), ConnectionError> is not dropped. Finally, either justify or remove
#[allow(dead_code)] on RequestedPty, Pending, ChannelInner, Channel, Connection,
and ConnectionError with a short comment explaining why they remain unused or
remove the attribute if no longer needed.
- Around line 354-364: The spawned task currently calls
rpc::GetVersion.handle(...).await and discards its Result; update the spawn body
so the Result from rpc::GetVersion.handle is matched/handled (e.g., use if let
Err(e) = ... or match) and log or otherwise handle the ConnectionError instead
of ignoring it; specifically modify the async move block around
rpc::GetVersion.handle(c_hnd, |_req| { ... }).await to capture the returned
Result and call the appropriate logger (or connection error handler) with the
error details to ensure failures are not silently dropped.
---
Nitpick comments:
In `@crates/minimald/src/connection.rs`:
- Line 37: There are unexplained #[allow(dead_code)] attributes in this module;
for each occurrence of #[allow(dead_code)] either remove the attribute and
tighten the API (make the item private or delete unused functions/structs), or
keep it but add a one-line justification comment immediately next to the
attribute (e.g., "// allow(dead_code): exported for benches/tests/feature-gate
X" or "// allow(dead_code): used only in FFI/extern tests") — alternatively
prefer using #[cfg(test)] or #[cfg(feature = "...")] on the item instead of
blanket dead_code suppression; update the occurrences of #[allow(dead_code)]
accordingly so every suppression has a clear justification or is eliminated.
🪄 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: 27fb5b11-bfdc-4606-bace-7756a7620e57
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
Cargo.tomlcrates/minimald/Cargo.tomlcrates/minimald/src/connection.rscrates/minimald/src/main.rscrates/minimald/src/rpc.rscrates/minimald/src/server.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- crates/minimald/Cargo.toml
- crates/minimald/src/rpc.rs
- crates/minimald/src/server.rs
- crates/minimald/src/main.rs
| pub(crate) async fn from_socket( | ||
| s: UnixStream, | ||
| c: Arc<RuConfig>, | ||
| serv: ServerStateHandle, | ||
| was_local_uds: bool, | ||
| ) -> (ConnectionHandle, RunningSession<ConnectionHandler>) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C2 '\bfrom_socket\s*\(' crates
rg -n -C2 '\brun_stream\s*\(' crates/minimald/src/connection.rsRepository: gominimal/minimal
Length of output: 970
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/connection.rs"
echo "== from_socket signature and run_stream block =="
sed -n '120,180p' "$file"
echo
echo "== find allow(dead_code) =="
rg -n "allow\(dead_code\)" "$file" || true
echo
echo "== find GetVersion spawn/task handling =="
rg -n "GetVersion" "$file" -n -C 3 || true
echo
echo "== search for dropped RPC task result patterns near tokio::spawn =="
rg -n "tokio::spawn" "$file" -C 4 || trueRepository: gominimal/minimal
Length of output: 3126
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/connection.rs"
echo "== allow(dead_code) contexts =="
for l in 37 47 56 68 120 194; do
echo "--- around line $l ---"
sed -n "$((l-3)),$((l+6))p" "$file"
done
echo
echo "== GetVersion spawn block end (look for dropped result) =="
sed -n '330,390p' "$file"
echo
echo "== from_socket call sites =="
sed -n '110,160p' crates/minimald/src/server.rsRepository: gominimal/minimal
Length of output: 4827
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/connection.rs"
echo "== imports involving spawn =="
rg -n "use .*spawn|tokio::spawn|JoinHandle|spawn\(" "$file" | head -n 200
echo
echo "== exact GetVersion handle call type context (few lines around 340-380) =="
sed -n '320,385p' "$file"
echo
echo "== definition/usages of ConnectionError in this file =="
rg -n "enum ConnectionError|type ConnectionError|ConnectionError" "$file" | head -n 50Repository: gominimal/minimal
Length of output: 2569
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "struct GetVersion|enum GetVersion|GetVersionResponse|mod rpc|pub mod rpc|GetVersion::NAME" crates/minimald crates | head -n 200
echo
echo "== find implementation of GetVersion::handle =="
rg -n "GetVersion.*handle\\(|impl .*GetVersion.*handle" crates | head -n 200
echo
echo "== locate rpc::GetVersion definition file =="
rg -n "GetVersionResponse" crates | head -n 200Repository: gominimal/minimal
Length of output: 904
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/rpc.rs"
sed -n '1,140p' "$file"
echo
echo "== search for handle method signature in rpc.rs =="
rg -n "fn handle\\b|async fn handle\\b|trait .*handle\\b" "$file"Repository: gominimal/minimal
Length of output: 2112
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/connection.rs"
rg -n "struct ConnectionHandler|ConnectionHandler\\(" "$file"
sed -n '180,310p' "$file"Repository: gominimal/minimal
Length of output: 4128
Propagate run_stream failures and handle RPC task errors instead of panicking/swallowing.
crates/minimald/src/connection.rs:Connection::from_socketpanics on handshake/socket failure viarussh::server::run_stream(...).await.unwrap()—return aResultand propagate the error with context.crates/minimald/src/connection.rs: therpc::GetVersion.handle(...).awaitresult is produced insidetokio::spawnbut theJoinHandleis dropped, discardingResult<(), ConnectionError>—log/handle the error.- Add justification comments (or remove) the
#[allow(dead_code)]attributes onRequestedPty,Pending,ChannelInner,Channel,Connection, andConnectionError.
🤖 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/connection.rs` around lines 134 - 139, Change
Connection::from_socket to return Result<(ConnectionHandle,
RunningSession<ConnectionHandler>), ConnectionError> instead of unwrapping
run_stream; propagate russh::server::run_stream(...) errors (map them into
ConnectionError with context) and return Err on failure rather than panicking.
For the RPC startup, capture the tokio::task::JoinHandle returned when calling
tokio::spawn for rpc::GetVersion.handle(...).await and either .await the
JoinHandle (and log/map any JoinError or inner ConnectionError) or spawn a small
task that logs the error if it fails, so the Result<(), ConnectionError> is not
dropped. Finally, either justify or remove #[allow(dead_code)] on RequestedPty,
Pending, ChannelInner, Channel, Connection, and ConnectionError with a short
comment explaining why they remain unused or remove the attribute if no longer
needed.
| spawn(async move { | ||
| rpc::GetVersion | ||
| .handle(c_hnd, |_req| { | ||
| Ok(rpc::GetVersionResponse { | ||
| version: env!("CARGO_PKG_VERSION").to_string(), | ||
| long_version: env!("LONG_VERSION").to_string(), | ||
| stdlib_version: stdlib::VERSION.to_string(), | ||
| }) | ||
| }) | ||
| .await | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C3 'trait OneshotSshRpc|fn handle' crates/minimald/src/rpc.rsRepository: gominimal/minimal
Length of output: 623
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- connection.rs around 340-390 ----"
nl -ba crates/minimald/src/connection.rs | sed -n '340,390p'
echo "---- Find GetVersion.handle usages ----"
rg -n 'GetVersion\s*\.handle' crates/minimald/src -SRepository: gominimal/minimal
Length of output: 161
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- file check ----"
ls -la crates/minimald/src/connection.rs || true
wc -l crates/minimald/src/connection.rs || true
echo "---- connection.rs lines 340-390 (no nl available) ----"
sed -n '340,390p' crates/minimald/src/connection.rs
echo "---- GetVersion.handle call sites ----"
rg -n "GetVersion\\s*\\.handle" crates/minimald/src -SRepository: gominimal/minimal
Length of output: 1702
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- connection.rs header (imports around spawn) ----"
sed -n '1,120p' crates/minimald/src/connection.rs
echo "---- locate spawn definition/usage ----"
rg -n "spawn\\(" crates/minimald/src/connection.rs
echo "---- inspect around spawn call in context ----"
sed -n '320,380p' crates/minimald/src/connection.rsRepository: gominimal/minimal
Length of output: 5448
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- rpc.rs: locate OneshotSshRpc::handle implementation ----"
rg -n "async fn handle\\b" crates/minimald/src/rpc.rs
echo "---- rpc.rs: show around the handle() body ----"
# print a window around the first match
line=$(rg -n "async fn handle\\b" crates/minimald/src/rpc.rs | head -n1 | cut -d: -f1)
start=$((line-40))
end=$((line+120))
sed -n "${start},${end}p" crates/minimald/src/rpc.rs
echo "---- rpc.rs: locate ConnectionError definition ----"
rg -n "enum ConnectionError|struct ConnectionError|type ConnectionError" crates/minimald/src/rpc.rs crates/minimald/src/connection.rs crates/minimald/src -SRepository: gominimal/minimal
Length of output: 2107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="crates/minimald/src/rpc.rs"
echo "---- rpc.rs: show first ~220 lines (fast, bounded) ----"
sed -n '1,220p' "$file"
echo "---- rpc.rs: locate exact OneshotSshRpc::handle line number ----"
handle_line="$(rg -n "async fn handle<F>" "$file" | head -n1 | cut -d: -f1)"
echo "handle_line=$handle_line"
start=$((handle_line-40))
if [ "$start" -lt 1 ]; then start=1; fi
end=$((handle_line+160))
echo "---- rpc.rs: show around handle() [$start,$end] ----"
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e {print}' "$file"
echo "---- rpc.rs: locate ConnectionError definition ----"
rg -n "ConnectionError" crates/minimald/src/rpc.rs crates/minimald/src/connection.rs -SRepository: gominimal/minimal
Length of output: 5003
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- connection.rs: ConnectionError definition ----"
sed -n '160,260p' crates/minimald/src/connection.rs
echo "---- find other spawn(async move { rpc:: ... .handle ... .await } patterns ----"
rg -n "spawn\\(async move \\{[\\s\\S]*rpc::.*\\.handle\\(" crates/minimald/src/connection.rs
rg -n "rpc::[A-Za-z0-9_]+\\s*\\n?\\.handle" crates/minimald/src/connection.rs
echo "---- find other uses of OneshotSshRpc::handle in repo ----"
rg -n "\\.handle\\(c_[a-zA-Z0-9_]+,|OneshotSshRpc" crates/minimald/src -SRepository: gominimal/minimal
Length of output: 3317
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "---- find other spawn(async move { rpc:: ... .handle ... .await } patterns (connection.rs) ----"
rg -n "spawn\\(async move \\{[\\s\\S]*rpc::.*\\.handle\\(" crates/minimald/src/connection.rs -S || true
echo "---- find any other .handle(...) calls in minimald/src ----"
rg -n "\\.handle\\(" crates/minimald/src -S || trueRepository: gominimal/minimal
Length of output: 303
Handle GetVersion RPC errors in the spawned task instead of discarding the Result.
tokio::spawn(async move { ... }) awaits rpc::GetVersion.handle(...).await (returns Result<(), ConnectionError>), but the task’s JoinHandle is ignored, so any Err is never logged/handled.
Suggested fix
spawn(async move {
- rpc::GetVersion
+ if let Err(err) = rpc::GetVersion
.handle(c_hnd, |_req| {
Ok(rpc::GetVersionResponse {
version: env!("CARGO_PKG_VERSION").to_string(),
long_version: env!("LONG_VERSION").to_string(),
stdlib_version: stdlib::VERSION.to_string(),
})
})
- .await
+ .await
+ {
+ tracing::warn!(error = ?err, "GetVersion RPC failed");
+ }
});🤖 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/connection.rs` around lines 354 - 364, The spawned task
currently calls rpc::GetVersion.handle(...).await and discards its Result;
update the spawn body so the Result from rpc::GetVersion.handle is
matched/handled (e.g., use if let Err(e) = ... or match) and log or otherwise
handle the ConnectionError instead of ignoring it; specifically modify the async
move block around rpc::GetVersion.handle(c_hnd, |_req| { ... }).await to capture
the returned Result and call the appropriate logger (or connection error
handler) with the error details to ensure failures are not silently dropped.
| use serde::{Deserialize, Serialize}; | ||
| use std::path::PathBuf; | ||
| use std::sync::Arc; | ||
| use std::sync::{Arc, Mutex}; |
There was a problem hiding this comment.
Also should use the tokio version
837b2ca to
01ef124
Compare
01ef124 to
1c7cf3f
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
crates/minimald/src/main.rs (1)
26-30:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
~is still treated as a literal path component in both fallbacks.If
dirs::state_dir()ordirs::cache_dir()returnsNone, these branches resolve to./~/.local/..., not the caller's home directory. Please build the fallback fromdirs::home_dir()instead.Proposed fix
pub fn minimal_state_dir(&self) -> PathBuf { self.global_args.minimal_dir.clone().unwrap_or_else(|| { dirs::state_dir() - .unwrap_or_else(|| PathBuf::from("~/.local/state")) + .or_else(|| dirs::home_dir().map(|home| home.join(".local/state"))) + .expect("could not determine state directory") .join("minimal") }) } @@ pub fn minimal_cache_dir(&self) -> PathBuf { dirs::cache_dir() - .unwrap_or_else(|| PathBuf::from("~/.local/cache")) + .or_else(|| dirs::home_dir().map(|home| home.join(".local/cache"))) + .expect("could not determine cache directory") .join("minimal") }Also applies to: 37-39
🤖 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/main.rs` around lines 26 - 30, The fallback branches in minimal_state_dir (and the similar minimal_cache_dir) currently use PathBuf::from("~/.local/...") which treats "~" literally; change them to construct the fallback from dirs::home_dir() and then join the ".local/state" (for minimal_state_dir) or ".local/cache" (for minimal_cache_dir) and then "minimal" so the user's actual home directory is used; update the unwrap_or_else paths to call dirs::home_dir(), build the proper joined PathBuf, and only fall back to a literal if home_dir() is None.
🧹 Nitpick comments (2)
crates/minimald/src/lib.rs (1)
8-13: ⚡ Quick winConsider adding
#[non_exhaustive]to future-proof the public API.Since
RequestedPtyis public and represents PTY parameters that could grow with new terminal features, marking it#[non_exhaustive]would allow adding fields in the future without breaking changes. As per coding guidelines: "Apply#[non_exhaustive]to public enums and structs that may grow".🔮 Proposed enhancement
/// Represents the parameters of a requested PTY. #[derive(Debug, Clone)] +#[non_exhaustive] #[allow(dead_code)] pub struct RequestedPty {🤖 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/lib.rs` around lines 8 - 13, Add the Rust attribute #[non_exhaustive] to the public struct RequestedPty so new fields can be added later without breaking downstream consumers; locate the declaration of pub struct RequestedPty and place #[non_exhaustive] immediately above it (keeping the struct public and its existing fields unchanged).crates/minimald/src/main.rs (1)
134-155: ⚡ Quick winUse
tokio::fsfor the startup filesystem work.These directory/socket operations now run inside
async fn main, but they still use blockingstd::fscalls. Swapping them totokio::fskeeps startup I/O off the runtime thread. As per coding guidelines, "In async contexts, avoid blocking operations: do not use std::fs, std::thread::sleep, blocking network, or sync Mutex held across.await; use tokio::fs, tokio::time::sleep, tokio::sync::Mutex instead".🤖 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/main.rs` around lines 134 - 155, The startup code in async fn main uses blocking std::fs calls; replace std::fs::create_dir_all and std::fs::remove_file with their async tokio::fs equivalents and await them so startup I/O doesn't block the runtime. Concretely, call tokio::fs::create_dir_all(cli.minimal_state_dir()).await and tokio::fs::create_dir_all(parent).await for the socket parent, and tokio::fs::remove_file(cli.listen_on()).await when removing the socket; keep the existing error checks (e.kind() comparisons) and return Err(MainError::IO(e, "...")) on failure to preserve behavior, and adjust any control flow to accommodate the .await points inside async fn main.
🤖 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/connection.rs`:
- Around line 314-315: Remove the ownerless TODOs in the request handlers by
replacing the ambiguous "// TODO: handle exec request" (near
session.channel_failure(id)? and the similar note on lines 324-325) with an
explicit, tracked action: either implement the exec handling now or mark the
path as intentionally unsupported and/or reference a tracked issue and owner.
Concretely, update the handler around session.channel_failure(id)? to do one of
the following: implement the exec request logic in the function that processes
exec requests, or replace the TODO with a clear comment like "intentionally
unsupported" plus an issue/owner tag (e.g., "ISSUE-1234 `@owner`") and ensure the
code returns a deterministic response (e.g., call session.channel_failure(id)?
and return an appropriate Err or Ok) so there are no ownerless TODOs left; apply
the same treatment to the other similar spot at lines 324-325.
In `@crates/minimald/src/lib.rs`:
- Line 7: Add a short justification comment immediately above the
#[allow(dead_code)] attribute that explains why the lint is suppressed (for
example: this struct is scaffolding for a higher layer / used only by tests /
will be referenced by FFI later), and include the specific item name it applies
to (the struct declared below the attribute); if the attribute is no longer
needed because the struct is now used, remove the #[allow(dead_code)] instead.
In `@crates/minimald/src/main.rs`:
- Around line 160-163: The logged SSH command in the tracing::info! call
currently suggests an interactive session ("ssh ... local") which is
unsupported; update the message emitted where tracing::info! is called (using
cli.listen_on().display() as the address) to either (a) log a transport-only
debug command that matches the supported SSH flow, e.g. use the subsystem form
with -sT and the minimald RPC name (minimald-v1-GetVersion) instead of an
interactive shell, or (b) explicitly annotate the existing example as
"transport-only debugging" so callers know shell/exec will be rejected; adjust
the tracing::info! message text accordingly.
- Around line 81-82: The help text for the CLI flag --minimal-dir incorrectly
references $XDG_STATE_DIR; update the docstring above the #[arg(long)] for the
--minimal-dir argument to read $XDG_STATE_HOME/minimal (since dirs::state_dir()
uses $XDG_STATE_HOME with fallback ~/.local/state), i.e., change the comment
"default: $XDG_STATE_DIR/minimal" to "default: $XDG_STATE_HOME/minimal" next to
the --minimal-dir arg so the help output is accurate.
---
Duplicate comments:
In `@crates/minimald/src/main.rs`:
- Around line 26-30: The fallback branches in minimal_state_dir (and the similar
minimal_cache_dir) currently use PathBuf::from("~/.local/...") which treats "~"
literally; change them to construct the fallback from dirs::home_dir() and then
join the ".local/state" (for minimal_state_dir) or ".local/cache" (for
minimal_cache_dir) and then "minimal" so the user's actual home directory is
used; update the unwrap_or_else paths to call dirs::home_dir(), build the proper
joined PathBuf, and only fall back to a literal if home_dir() is None.
---
Nitpick comments:
In `@crates/minimald/src/lib.rs`:
- Around line 8-13: Add the Rust attribute #[non_exhaustive] to the public
struct RequestedPty so new fields can be added later without breaking downstream
consumers; locate the declaration of pub struct RequestedPty and place
#[non_exhaustive] immediately above it (keeping the struct public and its
existing fields unchanged).
In `@crates/minimald/src/main.rs`:
- Around line 134-155: The startup code in async fn main uses blocking std::fs
calls; replace std::fs::create_dir_all and std::fs::remove_file with their async
tokio::fs equivalents and await them so startup I/O doesn't block the runtime.
Concretely, call tokio::fs::create_dir_all(cli.minimal_state_dir()).await and
tokio::fs::create_dir_all(parent).await for the socket parent, and
tokio::fs::remove_file(cli.listen_on()).await when removing the socket; keep the
existing error checks (e.kind() comparisons) and return Err(MainError::IO(e,
"...")) on failure to preserve behavior, and adjust any control flow to
accommodate the .await points inside async fn main.
🪄 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: c1e4b656-f3fc-441c-937a-949934732fe5
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
Cargo.tomlcrates/minimald/Cargo.tomlcrates/minimald/src/connection.rscrates/minimald/src/lib.rscrates/minimald/src/main.rscrates/minimald/src/rpc.rscrates/minimald/src/server.rs
✅ Files skipped from review due to trivial changes (1)
- Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (3)
- crates/minimald/src/rpc.rs
- crates/minimald/Cargo.toml
- crates/minimald/src/server.rs
| session.channel_failure(id)?; // TODO: handle exec request | ||
| Ok(()) |
There was a problem hiding this comment.
Remove ownerless TODOs in request handlers.
Line 314 and Line 324 contain unowned TODO comments. Either implement these paths now or replace with a tracked/owned reference (or an explicit “intentionally unsupported” note).
Suggested minimal cleanup
- session.channel_failure(id)?; // TODO: handle exec request
+ session.channel_failure(id)?; // intentionally unsupported for now
- session.channel_failure(id)?; // TODO: handle shell request
+ session.channel_failure(id)?; // intentionally unsupported for nowAs per coding guidelines "Do not include dead code, commented-out code, or ownerless TODOs".
Also applies to: 324-325
🤖 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/connection.rs` around lines 314 - 315, Remove the
ownerless TODOs in the request handlers by replacing the ambiguous "// TODO:
handle exec request" (near session.channel_failure(id)? and the similar note on
lines 324-325) with an explicit, tracked action: either implement the exec
handling now or mark the path as intentionally unsupported and/or reference a
tracked issue and owner. Concretely, update the handler around
session.channel_failure(id)? to do one of the following: implement the exec
request logic in the function that processes exec requests, or replace the TODO
with a clear comment like "intentionally unsupported" plus an issue/owner tag
(e.g., "ISSUE-1234 `@owner`") and ensure the code returns a deterministic response
(e.g., call session.channel_failure(id)? and return an appropriate Err or Ok) so
there are no ownerless TODOs left; apply the same treatment to the other similar
spot at lines 324-325.
|
|
||
| /// Represents the parameters of a requested PTY. | ||
| #[derive(Debug, Clone)] | ||
| #[allow(dead_code)] |
There was a problem hiding this comment.
Add a comment justifying the #[allow(dead_code)] attribute.
Per coding guidelines, any #[allow(...)] attribute must be justified with a comment. As per coding guidelines: "Justify any #[allow(...)] attributes with a comment explaining why the lint is suppressed".
If this struct is scaffolding that will be used in a subsequent layer, document that. Otherwise, consider removing the attribute once the struct is wired into the codebase.
📝 Proposed fix
+// Allow dead_code during scaffolding phase; struct will be used by connection handlers
#[allow(dead_code)]
pub struct RequestedPty {📝 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.
| #[allow(dead_code)] | |
| // Allow dead_code during scaffolding phase; struct will be used by connection handlers | |
| #[allow(dead_code)] | |
| pub struct RequestedPty { |
🤖 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/lib.rs` at line 7, Add a short justification comment
immediately above the #[allow(dead_code)] attribute that explains why the lint
is suppressed (for example: this struct is scaffolding for a higher layer / used
only by tests / will be referenced by FFI later), and include the specific item
name it applies to (the struct declared below the attribute); if the attribute
is no longer needed because the struct is now used, remove the
#[allow(dead_code)] instead.
| /// Override the state directory used for operations (default: $XDG_STATE_DIR/minimal) | ||
| #[arg(long)] |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant section around the reported lines
sed -n '60,110p' crates/minimald/src/main.rs | nl -baRepository: gominimal/minimal
Length of output: 104
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect around the reported lines (approx.)
sed -n '70,95p' crates/minimald/src/main.rs | cat -nRepository: gominimal/minimal
Length of output: 1105
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "state_dir\\(|XDG_STATE" crates/minimald/src/main.rsRepository: gominimal/minimal
Length of output: 405
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '15,65p' crates/minimald/src/main.rs | cat -nRepository: gominimal/minimal
Length of output: 2097
🌐 Web query:
dirs::state_dir environment variable XDG_STATE_HOME XDG_STATE_DIR
💡 Result:
XDG_STATE_HOME: According to the freedesktop.org XDG Base Directory Specification, $XDG_STATE_HOME defines the base directory relative to which user-specific state files should be stored; if it’s unset or empty, the default is $HOME/.local/state. The specification describes the intended contents as “state data that should persist between (application) restarts” (e.g., action history such as logs/history/recently used files, and current application state that can be reused on a restart) [1][2]. XDG_STATE_DIR: There is no XDG Base Directory Specification variable named $XDG_STATE_DIR. The specification defines $XDG_STATE_HOME (not a plural “DIR”), alongside other variables like $XDG_DATA_HOME, $XDG_CONFIG_HOME, $XDG_CACHE_HOME, etc. [2][3]. dirs::state_dir / “dirs::state_dir environment variable”: In Rust, the commonly used “dirs/dirs-next”-style APIs typically map to the XDG Base Directory variables; for state they correspond to XDG_STATE_HOME (with fallback to $HOME/.local/state) rather than a separate XDG_STATE_DIR. (A concrete example of such mapping is shown in a small XDG library implementation that reads XDG_STATE_HOME and falls back to $HOME/.local/state.) [4] In short: - Use $XDG_STATE_HOME to store persistent per-user application “state” as defined by the XDG spec (default: $HOME/.local/state). [1][2] - Treat $XDG_STATE_DIR as non-standard/undefined by the XDG Base Directory Specification. [2][3] - Any “dirs::state_dir” helper you encounter should resolve via XDG_STATE_HOME + the same default fallback. [4]
Citations:
- 1: https://xdg-specs-razzeee-a1dec53d4dde6f1df1b4b4e5aedd619a0802ae9768a5f.pages.freedesktop.org/basedir-spec/basedir-spec-latest.html
- 2: https://specifications.freedesktop.org/basedir/
- 3: https://xdg-specs-technobaboo-f55ac9d85e73073a0c8831695ba0fb110849811c0.pages.freedesktop.org/basedir-spec/latest/ar01s02.html
- 4: https://github.com/takluyver/pyxdg/blob/master/xdg/BaseDirectory.py
Fix the XDG variable name in the minimald CLI help text.
dirs::state_dir() is based on $XDG_STATE_HOME (fallback: ~/.local/state), so advertising $XDG_STATE_DIR/minimal in the --minimal-dir help text is misleading. Update the docstring default to $XDG_STATE_HOME/minimal instead.
🤖 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/main.rs` around lines 81 - 82, The help text for the CLI
flag --minimal-dir incorrectly references $XDG_STATE_DIR; update the docstring
above the #[arg(long)] for the --minimal-dir argument to read
$XDG_STATE_HOME/minimal (since dirs::state_dir() uses $XDG_STATE_HOME with
fallback ~/.local/state), i.e., change the comment "default:
$XDG_STATE_DIR/minimal" to "default: $XDG_STATE_HOME/minimal" next to the
--minimal-dir arg so the help output is accurate.
| tracing::info!( | ||
| "Run the following to debug the socket:\n\nssh -o ProxyCommand='socat - UNIX-CONNECT:{}' \\\n\t-o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' \\\n\tlocal", | ||
| cli.listen_on().display() | ||
| ); |
There was a problem hiding this comment.
Log a command that matches the supported SSH flow.
This hint prints plain ssh ... local, which requests an interactive session. Per the current PR objectives, shell/exec are rejected and the working path is subsystem RPCs, so this message is steering users to a failing flow. Log the -sT ... local minimald-v1-GetVersion example instead, or explicitly label this as transport-only debugging.
🤖 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/main.rs` around lines 160 - 163, The logged SSH command
in the tracing::info! call currently suggests an interactive session ("ssh ...
local") which is unsupported; update the message emitted where tracing::info! is
called (using cli.listen_on().display() as the address) to either (a) log a
transport-only debug command that matches the supported SSH flow, e.g. use the
subsystem form with -sT and the minimald RPC name (minimald-v1-GetVersion)
instead of an interactive shell, or (b) explicitly annotate the existing example
as "transport-only debugging" so callers know shell/exec will be rejected;
adjust the tracing::info! message text accordingly.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
crates/minimald/src/connection.rs (1)
54-76: ⚡ Quick win
closednever influences behavior here.
handle_channel_close()removes the channel fromself.channelsbefore settingclosed = true, so thec.closedbranches inpending_config_mut()andtake()are unreachable. Either keep closed entries around and mark them, or drop the flag and the dead branches.Also applies to: 144-159, 162-167
🤖 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/connection.rs` around lines 54 - 76, The Channel struct's closed flag is currently unused because handle_channel_close() removes the channel from the container before setting closed = true, making the c.closed checks in pending_config_mut() and take() unreachable; fix by either (A) preserving entries when a channel is closed and set self.closed = true inside Channel::handle_channel_close() before or instead of removal so pending_config_mut() and take() can observe closed, or (B) remove the dead flag and the c.closed branches from pending_config_mut() and take() (and delete the closed field and any references) so behavior is consistent; locate Channel::new_session, Channel::handle_channel_close, pending_config_mut, and take to apply one coherent approach.
🤖 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/connection.rs`:
- Around line 252-253: The protocol tracing currently prints sensitive contents:
change calls to protocol_trace! that log env_request and exec_request payloads
so they never include secret values; for example, in the env_request site (where
id, var_name, var_value are used) log only id and var_name and replace var_value
with a constant like "<redacted>" or omit it entirely, and in the exec_request
site stop serializing the full payload and instead log only metadata (command
name, args count, id) or a redacted summary; update the protocol_trace!
invocations referenced by env_request and exec_request to avoid dumping raw
values (also apply the same redaction approach to the other occurrences
mentioned).
In `@crates/minimald/src/lib.rs`:
- Around line 7-23: RequestedPty and ChannelConfig are exported as pub but all
fields are private, unnecessarily widening the public API; change both struct
declarations from pub struct to pub(crate) struct (and similarly change any
pub(crate) field visibility only if you intend external access later), keeping
derives and #[allow(dead_code)] intact—this confines the types to the crate
until you add curated constructors/accessors and re-export them from the crate
root via pub use.
In `@crates/minimald/src/main.rs`:
- Around line 134-149: The startup uses blocking std::fs::create_dir_all inside
the async main; replace those blocking calls with async
tokio::fs::create_dir_all(...).await (and tokio::fs::remove_file(...).await
where applicable) so they don't block the Tokio runtime. Update the two call
sites that reference std::fs::create_dir_all with tokio::fs equivalents and
await them (the ones using cli.minimal_state_dir() and cli.listen_on() parent),
preserve the existing error handling that maps to MainError::IO (return
Err(MainError::IO(e, "..."))), and add a tokio::fs import if needed. Ensure you
still check parent.is_some() and e.kind() != std::io::ErrorKind::AlreadyExists
when converting the awaited error into MainError::IO.
---
Nitpick comments:
In `@crates/minimald/src/connection.rs`:
- Around line 54-76: The Channel struct's closed flag is currently unused
because handle_channel_close() removes the channel from the container before
setting closed = true, making the c.closed checks in pending_config_mut() and
take() unreachable; fix by either (A) preserving entries when a channel is
closed and set self.closed = true inside Channel::handle_channel_close() before
or instead of removal so pending_config_mut() and take() can observe closed, or
(B) remove the dead flag and the c.closed branches from pending_config_mut() and
take() (and delete the closed field and any references) so behavior is
consistent; locate Channel::new_session, Channel::handle_channel_close,
pending_config_mut, and take to apply one coherent approach.
🪄 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: 3ccd3bd4-70b0-4884-a0d8-fc376079da9a
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
Cargo.tomlcrates/minimald/Cargo.tomlcrates/minimald/src/connection.rscrates/minimald/src/lib.rscrates/minimald/src/main.rscrates/minimald/src/rpc.rscrates/minimald/src/server.rs
✅ Files skipped from review due to trivial changes (2)
- Cargo.toml
- crates/minimald/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/minimald/src/rpc.rs
- crates/minimald/src/server.rs
| protocol_trace!("Got env_request on channel {id}: {var_name}={var_value}"); | ||
|
|
There was a problem hiding this comment.
Redact request payloads from protocol tracing.
env_request logs raw environment variable values and exec_request logs the full payload. Both can carry credentials, so enabling PROTOCOL_TRACE=1 turns tracing into a secret sink. Log only names/metadata, or explicitly redact values.
Also applies to: 302-305
🤖 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/connection.rs` around lines 252 - 253, The protocol
tracing currently prints sensitive contents: change calls to protocol_trace!
that log env_request and exec_request payloads so they never include secret
values; for example, in the env_request site (where id, var_name, var_value are
used) log only id and var_name and replace var_value with a constant like
"<redacted>" or omit it entirely, and in the exec_request site stop serializing
the full payload and instead log only metadata (command name, args count, id) or
a redacted summary; update the protocol_trace! invocations referenced by
env_request and exec_request to avoid dumping raw values (also apply the same
redaction approach to the other occurrences mentioned).
| /// Represents the parameters of a requested PTY. | ||
| #[derive(Debug, Clone)] | ||
| #[allow(dead_code)] | ||
| pub struct RequestedPty { | ||
| char_sizes: (u32, u32), | ||
| pixel_sizes: (u32, u32), | ||
| term: String, | ||
| modes: Vec<(russh::Pty, u32)>, | ||
| } | ||
|
|
||
| /// Represents the currently configured parameters for a channel being created. | ||
| #[derive(Debug)] | ||
| #[allow(dead_code)] | ||
| pub struct ChannelConfig { | ||
| pub(crate) env_vars: BTreeMap<String, String>, | ||
| pub(crate) pty: Option<RequestedPty>, | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Avoid exporting scaffolding types before the API is usable.
RequestedPty and ChannelConfig are public, but downstream code cannot meaningfully construct or inspect them because all fields are private/crate-private. That widens the semver surface without providing a real API. Prefer pub(crate) for now, then re-expose curated constructors/accessors once these types are intentionally supported outside the crate. As per coding guidelines "Make items private by default; widen to pub(crate) before pub; curate the public API via pub use at the crate root".
🤖 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/lib.rs` around lines 7 - 23, RequestedPty and
ChannelConfig are exported as pub but all fields are private, unnecessarily
widening the public API; change both struct declarations from pub struct to
pub(crate) struct (and similarly change any pub(crate) field visibility only if
you intend external access later), keeping derives and #[allow(dead_code)]
intact—this confines the types to the crate until you add curated
constructors/accessors and re-export them from the crate root via pub use.
| if let Err(e) = std::fs::create_dir_all(cli.minimal_state_dir()) | ||
| && e.kind() != std::io::ErrorKind::AlreadyExists | ||
| { | ||
| return Err(MainError::IO(e, "creating minimal dir")); | ||
| } | ||
|
|
||
| // If we got this far we need to launch minimald. | ||
| // | ||
| // Ensure the socket's parent directory exists. | ||
| let socket_path = cli.listen_on(); | ||
| if let Some(parent) = socket_path.parent() | ||
| && let Err(e) = std::fs::create_dir_all(parent) | ||
| && e.kind() != std::io::ErrorKind::AlreadyExists | ||
| { | ||
| return Err(MainError::IO(e, "creating provider dir")); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Tokio async code, is it recommended to use tokio::fsinstead ofstd::fs for filesystem operations on the runtime thread?
💡 Result:
Recommended: yes—inside Tokio async code, prefer tokio::fs (or other Tokio async APIs) over calling std::fs directly from the runtime worker thread. Why: - Most OSes don’t provide true async filesystem APIs, so Tokio implements tokio::fs by running ordinary blocking file syscalls in the spawn_blocking threadpool, keeping the async runtime responsive instead of blocking the executor thread [1]. - Tokio’s filesystem module explicitly warns that “tasks run by worker threads should not block,” and provides adapters (including tokio::fs) that shift blocking operations to a backup thread when needed [2]. - Tokio also notes that tokio::fs uses spawn_blocking and can have serious performance consequences; therefore you should batch/limit the number of spawn_blocking operations (e.g., avoid doing many small filesystem ops sequentially) [1]. When std::fs is acceptable: - It’s generally fine to use std::fs during startup/initialization before the async runtime is running (since you’re not blocking an executor worker thread) [3].
Citations:
- 1: https://docs.rs/tokio/latest/tokio/fs/
- 2: https://docs.rs/tokio-fs/latest/tokio_fs/
- 3: https://github.com/leonardomso/rust-skills/blob/HEAD/rules/async-tokio-fs.md
Use tokio::fs for the startup filesystem work in crates/minimald/src/main.rs.
Those std::fs::{create_dir_all, remove_file} calls run inside #[tokio::main] and block a Tokio worker thread; switch to tokio::fs::*().await on the async startup path.
♻️ Suggested change
- if let Err(e) = std::fs::create_dir_all(cli.minimal_state_dir())
+ if let Err(e) = tokio::fs::create_dir_all(cli.minimal_state_dir()).await
&& e.kind() != std::io::ErrorKind::AlreadyExists
{
return Err(MainError::IO(e, "creating minimal dir"));
}
@@
let socket_path = cli.listen_on();
if let Some(parent) = socket_path.parent()
- && let Err(e) = std::fs::create_dir_all(parent)
+ && let Err(e) = tokio::fs::create_dir_all(parent).await
&& e.kind() != std::io::ErrorKind::AlreadyExists
{
return Err(MainError::IO(e, "creating provider dir"));
}
@@
// Listen on the UDS socket.
- if let Err(e) = std::fs::remove_file(cli.listen_on())
+ if let Err(e) = tokio::fs::remove_file(cli.listen_on()).await
&& e.kind() != std::io::ErrorKind::NotFound
{
return Err(MainError::IO(e, "socket already in use"));
}🤖 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/main.rs` around lines 134 - 149, The startup uses
blocking std::fs::create_dir_all inside the async main; replace those blocking
calls with async tokio::fs::create_dir_all(...).await (and
tokio::fs::remove_file(...).await where applicable) so they don't block the
Tokio runtime. Update the two call sites that reference std::fs::create_dir_all
with tokio::fs equivalents and await them (the ones using
cli.minimal_state_dir() and cli.listen_on() parent), preserve the existing error
handling that maps to MainError::IO (return Err(MainError::IO(e, "..."))), and
add a tokio::fs import if needed. Ensure you still check parent.is_some() and
e.kind() != std::io::ErrorKind::AlreadyExists when converting the awaited error
into MainError::IO.
Basically, the connection handler type wraps the connection handle, and very quickly reaches into it via a lock to tweak state when managing the ssh connection. As requests for pty, env vars etc come in, we mutate this pending state (owned by the Connection) to accumlate the configurables, sorta like the builder pattern.
When its time for the channel to actually be wired to something heavy, we take the
RuChannel<Msg>out of the connection state, and it lives with the async context which actually does the work. The handler method returns quickly, freeing up the thread which services the connection once again.Summary by CodeRabbit
New Features
Chores