feat: always set certain important variables automatically - #1060
Conversation
📝 WalkthroughWalkthroughThe change defers inherited session environment resolution to clients, propagates attach-time variables into sandbox launches, adds Linux procfs network fallbacks, handles cross-device hardlinking, and updates session and sandbox configuration behavior. ChangesSession environment propagation
Diagnostics and network handling
Cross-device materialization
Sandbox quota handling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SSHClient
participant Minimald
participant SessionComposer
participant SandboxLauncher
SSHClient->>Minimald: attach with forwarded environment
Minimald->>Minimald: filter inherited variables and add TERM
Minimald->>SessionComposer: compose session variables
SessionComposer-->>Minimald: preserve inherited specifications
Minimald->>SandboxLauncher: launch with AttachEnv
SandboxLauncher->>SandboxLauncher: layer environment variables
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
| // is a fixed filesystem-layout fact, not a per-file | ||
| // condition. | ||
| use std::sync::atomic::{AtomicBool, Ordering}; | ||
| static WARNED: AtomicBool = AtomicBool::new(false); |
There was a problem hiding this comment.
Statics make me sad but I guess theres no way to do this cleaner without it being more verbose than its worth
There was a problem hiding this comment.
Yeah, I don't really like it either. I don't think we want the log spammed with every single file being copied though. There's probably a better way to do this, but we don't have a ton of time today.
| } else { | ||
| Err(e) | ||
| } | ||
| fs::copy(&path, &dst_path).map(|_| ()) |
There was a problem hiding this comment.
Im also not sure how I feel about this, maybe we are far enough along that its better to be slow and fallback to a copy, but also my other thought is this might mask when your paths are wrong
There was a problem hiding this comment.
That's a good point. This is really only required for nested sandboxes, and there may be a better way. We should probably change it to be a preference, but I'll cut an issue for it given timing.
There was a problem hiding this comment.
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/common/src/lib.rs`:
- Around line 224-242: Update the EXDEV fallback in the hard-linking flow to
avoid writing directly to the final destination, preserving an existing
destination if it appears after the hard_link attempt. Copy through an
exclusively created temporary file and rename it into place, or use an
equivalent create_new flow; treat AlreadyExists as successful completion,
matching the hard-link path.
🪄 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: 4abbb143-b1a5-4adc-9ae4-966f05ba9a78
📒 Files selected for processing (13)
.minimal/minimal.tomlcrates/common/src/lib.rscrates/diagnostics/src/net.rscrates/minimal/src/lib.rscrates/minimald/src/diag.rscrates/minimald/src/net/switch.rscrates/minimald/src/session.rscrates/minimald/src/session_host.rscrates/sandbox2/src/lib.rscrates/sessions/example_project/minimal.tomlcrates/sessions/src/core/compose.rscrates/sessions/src/core/primitives.rscrates/sessions/src/daemon/composer.rs
| Err(e) if e.raw_os_error() == Some(libc::EXDEV) => { | ||
| // Every file in a cross-device tree hits EXDEV, so warn only | ||
| // on the first — a per-file log would flood with thousands | ||
| // of identical lines. Once-per-process is enough: the cause | ||
| // is a fixed filesystem-layout fact, not a per-file | ||
| // condition. | ||
| use std::sync::atomic::{AtomicBool, Ordering}; | ||
| static WARNED: AtomicBool = AtomicBool::new(false); | ||
| if !WARNED.swap(true, Ordering::Relaxed) { | ||
| warn!( | ||
| "Not linking {} => {}, already exists", | ||
| "Copying instead of hardlinking: cache and \ | ||
| destination are on different filesystems; further \ | ||
| cross-device copies this run are silent (first: {} \ | ||
| => {})", | ||
| path.display(), | ||
| dst_path.display() | ||
| ); | ||
| Ok(()) | ||
| } else { | ||
| Err(e) | ||
| } | ||
| fs::copy(&path, &dst_path).map(|_| ()) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wc -l crates/common/src/lib.rs
sed -n '180,280p' crates/common/src/lib.rs | cat -nRepository: gominimal/minimal
Length of output: 5044
Preserve the existing-destination contract in the EXDEV fallback.
fs::copy(&path, &dst_path) writes to the final path directly, so a destination that appears after hard_link returns EXDEV can be overwritten. Use an exclusively created temp file or create_new/rename flow here, and treat AlreadyExists as success just like the hard-link path.
🤖 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/common/src/lib.rs` around lines 224 - 242, Update the EXDEV fallback
in the hard-linking flow to avoid writing directly to the final destination,
preserving an existing destination if it appears after the hard_link attempt.
Copy through an exclusively created temporary file and rename it into place, or
use an equivalent create_new flow; treat AlreadyExists as successful completion,
matching the hard-link path.
56006c6 to
7ad863b
Compare
The merge commit c0f9a4f resolved conflicts in crates/minimal/src/lib.rs by taking the pre-merge side, silently reverting two fixes that had landed on main: - #1053 — the `min init` refuse-to-overwrite guard: `InitArgs::force`, the bail on an existing `minimal.toml`, and the Updated/Created wording. Without it `min init` overwrites an existing `minimal.toml` with no backup and no prompt, which is the data loss #1032 was filed for. - #1060 — the `SHELL=/bin/sh` pin on the ssh ProxyCommand and the `SendEnv` forwarding of LANG/LC_*/TZ. Without the pin, attach dies at "banner exchange ... Broken pipe" for any user whose $SHELL is a bare name (fish) or absent from the ssh context. Neither revert was caught by the suite: there is no test for the init guard, and `interactive_attach_requires_a_tty_on_stdin` only asserts the error contains "not a TTY" and "--command", which holds either way. Rebuilt lib.rs from main and reapplied only the intended change, so the diff against main is now exactly the `hide = true` on `AttachArgs::command` plus the non-TTY error reword: +4/-3, was +9/-51. Verified: rustfmt clean; `cargo build -p minimal --locked` ok; `min session attach --help` no longer lists `-c`/`--command` while `--command` still parses; `min init --help` lists `--force` again. `cargo test -p minimal` cannot run on macOS (dev-deps pull minimald -> procfs/caps, Linux-only), so the suite is left to CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…surface (#1071) * chore(minimal): hide `min session attach --command` from help Add `hide = true` to the `#[arg(long, short)]` attribute on `AttachArgs::command` so `-c` / `--command` no longer appear in `min session attach --help`, `-h`, or shell completions. The flag stays functional for existing scripted callers; the option promises a general remote exec it cannot deliver (no PTY, only three daemon commands accepted), so it should not be advertised for discovery. The non-TTY attach error still points stuck callers at `--command`; reword it to frame the flag as a deliberate hidden escape hatch rather than drop the reference, keeping the advice actionable now that the flag is off the help surface. * fix(minimal): restore #1053 and #1060 clobbered by the merge commit The merge commit c0f9a4f resolved conflicts in crates/minimal/src/lib.rs by taking the pre-merge side, silently reverting two fixes that had landed on main: - #1053 — the `min init` refuse-to-overwrite guard: `InitArgs::force`, the bail on an existing `minimal.toml`, and the Updated/Created wording. Without it `min init` overwrites an existing `minimal.toml` with no backup and no prompt, which is the data loss #1032 was filed for. - #1060 — the `SHELL=/bin/sh` pin on the ssh ProxyCommand and the `SendEnv` forwarding of LANG/LC_*/TZ. Without the pin, attach dies at "banner exchange ... Broken pipe" for any user whose $SHELL is a bare name (fish) or absent from the ssh context. Neither revert was caught by the suite: there is no test for the init guard, and `interactive_attach_requires_a_tty_on_stdin` only asserts the error contains "not a TTY" and "--command", which holds either way. Rebuilt lib.rs from main and reapplied only the intended change, so the diff against main is now exactly the `hide = true` on `AttachArgs::command` plus the non-TTY error reword: +4/-3, was +9/-51. Verified: rustfmt clean; `cargo build -p minimal --locked` ok; `min session attach --help` no longer lists `-c`/`--command` while `--command` still parses; `min init --help` lists `--force` again. `cargo test -p minimal` cannot run on macOS (dev-deps pull minimald -> procfs/caps, Linux-only), so the suite is left to CI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Norrie Taylor <91171431+norrietaylor@users.noreply.github.com> Co-authored-by: Norrie Taylor <norrie@minimal.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
Sessions now come up with the environment variables a usable interactive shell expects — login identity, a styled prompt, and correct locale/timezone — set automatically instead of being left unset or wrong. This also fixes a correctness bug in how the daemon resolves inherited variables: they're now resolved against the user's environment, never the daemon's.
Motivation
A freshly-attached session shell was missing conventional environment a login shell would normally get from PAM//etc/passwd/sshd: no USER/LOGNAME/SHELL, a hardcoded PS1 forced by the launcher, and a locale (en_US.utf8 + LC_ALL) that isn't generated in the session rootfs — producing "cannot set locale" warnings. Worse, project-declared Inherit vars (e.g. LANG, TZ) were resolved daemon-side against the daemon's process environment, so a session would silently pick up the daemon's locale/timezone rather than the connecting user's.
What changed
Session defaults now live in the sandbox, as overridable defaults (crates/sandbox2/src/lib.rs)
Client forwards locale/timezone; daemon accepts an allowlist (crates/minimal/src/lib.rs, crates/minimald/src/session.rs)
Environment is layered by precedence at attach (crates/minimald/src/session_host.rs, session.rs)
Inherited vars resolve against the user's env, not the daemon's (crates/sessions/src/core/{compose,primitives}.rs, daemon/composer.rs)
Supporting fixes
Tests
Note
Set PS1, USER, LOGNAME, SHELL, and LANG automatically in session shells and forward locale/timezone from client
PS1,USER,LOGNAME,SHELL=/usr/bin/bash, andLANG=C.UTF-8by default in sandbox2/src/lib.rs;LC_ALLis no longer set for sessions.ProxyCommandunder/bin/shand forwardsLANG,LC_*, andTZviaSendEnv.LANG,LC_*,TZ,TERM) and layers them into the session with composition vars and connection facts taking precedence.hardlink_dir_contentsin common/src/lib.rs now falls back to copying when source and destination are on different filesystems (EXDEV), emitting a one-time warning.SHELL,USER, orPS1will now see these set; composition-defined vars still override the defaults.Macroscope summarized 7ad863b.
Summary by CodeRabbit
justandprotobufto the default session packages./proc/netwhen system tools are unavailable.