chore: migrate to Rust edition 2024

Bump edition 2021 -> 2024. rust-version stays at 1.91, already well above
the 1.85 floor the edition needs; docs/guide/resources/troubleshooting.md
still claimed 1.70+, which is where a failed `cargo install --git` lands.

Four things the edition forces:

- std::env::{set_var,remove_var} are unsafe in 2024 with no safe std
  replacement. Rather than wrap the test call sites in unsafe -- which the
  crate denies and .semgrep.yml flags -- route them through temp-env, a
  dev-only dependency whose closure API is safe and which restores the
  previous value even when the body panics. The hand-rolled CLAUDE_DIR_LOCK
  and PI_DIR_LOCK guards existed only to serialise those mutations and are
  now redundant; CWD_LOCK and TEST_ENV_LOCK stay, they order more than the
  env var itself.

- unsafe_op_in_unsafe_fn is on by default, so the libc calls in the proxy
  signal handler and in stream.rs's relay handler need explicit unsafe
  blocks, scoped to the libc calls themselves.

- `gen` is a reserved keyword, so the closure by that name in diff_cmd.rs
  becomes make_lines.

- Tightened tail-expression temporary scopes let clippy prove the binding in
  setup_test_env is inlinable, so let_and_return now fires there.

if_let_rescope changes when the scrutinee temporary drops in an if let/else.
The two sites in show_claude_config take cargo fix --edition's match rewrite,
which keeps the 2021 drop timing.

rustfmt.toml is kept rather than dropped: cargo fmt passes --edition from
Cargo.toml, but a bare rustfmt invocation has no crate context and falls
back to edition 2015, which cannot parse the let-chains the next commit
introduces. Pinning it there keeps format-on-save and pre-commit hooks in
agreement with CI.

clippy::collapsible_if is allowed crate-wide for now; the follow-up commit
adopts let-chains and removes the allow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nicolas Le Cam
2026-08-31 01:19:25 +02:00
parent 70ec493d0d
commit 7c18567155
15 changed files with 276 additions and 226 deletions
Generated
+57
View File
@@ -723,6 +723,15 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
@@ -781,6 +790,29 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -863,6 +895,15 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.6"
@@ -939,6 +980,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"temp-env",
"tempfile",
"toml",
"toml_edit",
@@ -1025,6 +1067,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.27"
@@ -1159,6 +1207,15 @@ dependencies = [
"syn",
]
[[package]]
name = "temp-env"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050"
dependencies = [
"parking_lot",
]
[[package]]
name = "tempfile"
version = "3.26.0"
+7 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "rtk"
version = "0.48.0"
edition = "2021"
edition = "2024"
rust-version = "1.91"
authors = ["Patrick Szymkowiak"]
description = "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
@@ -54,6 +54,12 @@ windows-sys = { version = "0.59", features = [
[build-dependencies]
toml = "0.8"
[dev-dependencies]
# Safe, closure-scoped env mutation for tests. std::env::set_var is unsafe
# in edition 2024 and has no safe std replacement; temp-env restores the
# previous value on completion or panic and serializes callers internally.
temp-env = "0.3"
[profile.release]
opt-level = 3
lto = true
+1 -1
View File
@@ -144,7 +144,7 @@ cargo build --release
cargo install --path . --force
```
Minimum required Rust version: 1.70+.
Minimum required Rust version: 1.91 (edition 2024 needs Cargo 1.85 or newer).
## OpenCode not using RTK
+5
View File
@@ -1 +1,6 @@
# cargo fmt passes --edition from Cargo.toml, but a bare `rustfmt` invocation --
# editor format-on-save, rust-analyzer.rustfmt.overrideCommand, a pre-commit
# hook -- has no crate context and falls back to edition 2015, which cannot even
# parse this crate's let-chains. Pin it here so those agree with CI.
edition = "2024"
style_edition = "2024"
+3 -3
View File
@@ -7342,13 +7342,13 @@ diff --git a/b.rs b/b.rs
for _ in 0..3_000 {
let n = (lcg(&mut seed) % 12) as usize;
let m = (lcg(&mut seed) % 12) as usize;
let mut gen = |k: usize| -> Vec<String> {
let mut make_lines = |k: usize| -> Vec<String> {
(0..k)
.map(|_| format!("key{} = {}", lcg(&mut seed) % 3, lcg(&mut seed) % 5))
.collect()
};
let a_lines = gen(n);
let b_lines = gen(m);
let a_lines = make_lines(n);
let b_lines = make_lines(m);
let a: Vec<&str> = a_lines.iter().map(|s| s.as_str()).collect();
let b: Vec<&str> = b_lines.iter().map(|s| s.as_str()).collect();
+31 -29
View File
@@ -1346,13 +1346,14 @@ mod tests {
#[test]
fn native_run_returns_zero_on_success() {
let tmp = tempfile::tempdir().unwrap();
std::env::set_var("RTK_TEE_DIR", tmp.path());
std::fs::write(tmp.path().join("a.txt"), "x").unwrap();
let root = tmp.path().to_string_lossy().into_owned();
assert_eq!(
run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(),
0
);
temp_env::with_var("RTK_TEE_DIR", Some(tmp.path()), || {
std::fs::write(tmp.path().join("a.txt"), "x").unwrap();
let root = tmp.path().to_string_lossy().into_owned();
assert_eq!(
run("*.txt", &root, 10, false, None, "f", false, 0).unwrap(),
0
);
});
}
#[test]
@@ -1411,28 +1412,29 @@ mod tests {
#[test]
fn disclosure_survives_the_output_guard() {
let tee = tempfile::tempdir().unwrap();
std::env::set_var("RTK_TEE_DIR", tee.path());
let timer = tracking::TimedExecution::start();
let shown = render(
vec!["visible.txt".to_string()],
50,
false,
&["secret.txt".to_string()],
"find . -name '*.txt'",
"visible.txt",
&timer,
);
assert!(shown.contains("(1 filtered"), "{shown}");
let shown = render(
vec![],
50,
false,
&["secret.txt".to_string()],
"find . -name secret.txt",
"",
&timer,
);
assert!(shown.contains("(1 filtered"), "{shown}");
temp_env::with_var("RTK_TEE_DIR", Some(tee.path()), || {
let timer = tracking::TimedExecution::start();
let shown = render(
vec!["visible.txt".to_string()],
50,
false,
&["secret.txt".to_string()],
"find . -name '*.txt'",
"visible.txt",
&timer,
);
assert!(shown.contains("(1 filtered"), "{shown}");
let shown = render(
vec![],
50,
false,
&["secret.txt".to_string()],
"find . -name secret.txt",
"",
&timer,
);
assert!(shown.contains("(1 filtered"), "{shown}");
});
}
#[cfg(unix)]
+3 -3
View File
@@ -1266,9 +1266,9 @@ mod tests {
let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap();
let dir = tempfile::tempdir().unwrap();
let cfg = temp_cfg(dir.path());
std::env::set_var("RTK_RECALL", "0");
record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log");
std::env::remove_var("RTK_RECALL");
temp_env::with_var("RTK_RECALL", Some("0"), || {
record_tee_recall_with(&cfg, "grep", "/tee/1_grep.log");
});
assert!(
!dir.path().join("recall_test.db").exists(),
"RTK_RECALL=0 must prevent any recall.db write from the hook path"
+9 -3
View File
@@ -287,11 +287,17 @@ mod signal_relay {
unsafe extern "C" fn relay(sig: libc::c_int) {
let pid = CHILD_PID.load(Ordering::SeqCst);
if pid == 0 || RELAYED.swap(sig, Ordering::SeqCst) != 0 {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
// nosemgrep: unsafe-block
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
return;
}
libc::kill(pid as libc::pid_t, sig);
// nosemgrep: unsafe-block
unsafe {
libc::kill(pid as libc::pid_t, sig);
}
}
fn escalate(pid: u32) {
+7 -5
View File
@@ -113,12 +113,14 @@ mod tests {
#[test]
fn test_disabled_env_emits_nothing() {
let _guard = crate::core::utils::TEST_ENV_LOCK.lock().unwrap();
std::env::set_var("RTK_RECALL", "0");
let big = "x".repeat(1000);
let hint = tee_and_hint(&big, "cmd", 1);
let forced = force_tee_hint(&big, "cmd");
let tail = force_tee_tail_hint(&big, "cmd", 5);
std::env::remove_var("RTK_RECALL");
let (hint, forced, tail) = temp_env::with_var("RTK_RECALL", Some("0"), || {
(
tee_and_hint(&big, "cmd", 1),
force_tee_hint(&big, "cmd"),
force_tee_tail_hint(&big, "cmd", 5),
)
});
assert!(hint.is_none(), "disabled must never emit tokens");
assert!(forced.is_none());
assert!(tail.is_none());
+22 -21
View File
@@ -275,34 +275,35 @@ mod tests {
/// Regression for #1307: the env opt-out must short-circuit telemetry
/// consent paths so `rtk init` cannot hang in non-interactive environments.
/// All cases are bundled in one test to serialize env-var mutations.
/// All cases live in one test so the opt-out states cannot interleave.
#[test]
fn test_telemetry_disabled_by_env_honors_opt_out() {
#[allow(deprecated)]
std::env::remove_var(TELEMETRY_DISABLED_ENV);
assert!(
!telemetry_disabled_by_env(),
"unset env must not count as disabled"
);
temp_env::with_var_unset(TELEMETRY_DISABLED_ENV, || {
assert!(
!telemetry_disabled_by_env(),
"unset env must not count as disabled"
);
});
#[allow(deprecated)]
std::env::set_var(TELEMETRY_DISABLED_ENV, TELEMETRY_DISABLED_VALUE);
assert!(
telemetry_disabled_by_env(),
"RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)"
temp_env::with_var(
TELEMETRY_DISABLED_ENV,
Some(TELEMETRY_DISABLED_VALUE),
|| {
assert!(
telemetry_disabled_by_env(),
"RTK_TELEMETRY_DISABLED=1 must disable telemetry prompts (issue #1307)"
);
},
);
for other in ["0", "true", "false", "yes", "no", ""] {
#[allow(deprecated)]
std::env::set_var(TELEMETRY_DISABLED_ENV, other);
assert!(
!telemetry_disabled_by_env(),
"value {other:?} must not be treated as disabled"
);
temp_env::with_var(TELEMETRY_DISABLED_ENV, Some(other), || {
assert!(
!telemetry_disabled_by_env(),
"value {other:?} must not be treated as disabled"
);
});
}
#[allow(deprecated)]
std::env::remove_var(TELEMETRY_DISABLED_ENV);
}
// A canned 64-hex-char hash for deterministic `device_hash_line` assertions.
+50 -56
View File
@@ -2054,19 +2054,16 @@ mod tests {
));
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
env::set_var("RTK_DB_PATH", &db_path);
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
let timer = TimedExecution::start();
std::thread::sleep(std::time::Duration::from_millis(10));
timer.track("test cmd", "rtk test", "raw input data", "filtered");
let timer = TimedExecution::start();
std::thread::sleep(std::time::Duration::from_millis(10));
timer.track("test cmd", "rtk test", "raw input data", "filtered");
// Verify via DB that record exists
let tracker = Tracker::new().expect("Failed to create tracker");
let recent = tracker.get_recent(5).expect("Failed to get recent");
assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test"));
drop(tracker);
env::remove_var("RTK_DB_PATH");
// Verify via DB that record exists
let tracker = Tracker::new().expect("Failed to create tracker");
let recent = tracker.get_recent(5).expect("Failed to get recent");
assert!(recent.iter().any(|r| r.rtk_cmd == "rtk test"));
});
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
}
@@ -2083,49 +2080,48 @@ mod tests {
));
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
env::set_var("RTK_DB_PATH", &db_path);
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
let timer = TimedExecution::start();
timer.track_passthrough("git tag", "rtk git tag (passthrough)");
let timer = TimedExecution::start();
timer.track_passthrough("git tag", "rtk git tag (passthrough)");
let tracker = Tracker::new().expect("Failed to create tracker");
let recent = tracker.get_recent(5).expect("Failed to get recent");
let tracker = Tracker::new().expect("Failed to create tracker");
let recent = tracker.get_recent(5).expect("Failed to get recent");
let pt = recent
.iter()
.find(|r| r.rtk_cmd.contains("passthrough"))
.expect("Passthrough record not found");
let pt = recent
.iter()
.find(|r| r.rtk_cmd.contains("passthrough"))
.expect("Passthrough record not found");
// savings_pct should be 0 for passthrough
assert_eq!(pt.savings_pct, 0.0);
assert_eq!(pt.saved_tokens, 0);
drop(tracker);
env::remove_var("RTK_DB_PATH");
// savings_pct should be 0 for passthrough
assert_eq!(pt.savings_pct, 0.0);
assert_eq!(pt.saved_tokens, 0);
});
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
}
// 7. get_db_path respects environment variable RTK_DB_PATH
// 8. get_db_path falls back to default when no custom config
// Combined into one test to avoid env var race between parallel tests
// Combined into one test so the set and unset cases cannot interleave.
#[test]
fn test_db_path_env_and_default() {
use std::env;
let _guard = ENV_LOCK.lock().unwrap();
let custom_path = env::temp_dir().join("rtk_test_custom.db");
env::set_var("RTK_DB_PATH", &custom_path);
let db_path = get_db_path().expect("Failed to get db path");
assert_eq!(db_path, custom_path);
temp_env::with_var("RTK_DB_PATH", Some(&custom_path), || {
let db_path = get_db_path().expect("Failed to get db path");
assert_eq!(db_path, custom_path);
});
env::remove_var("RTK_DB_PATH");
let db_path = get_db_path().expect("Failed to get db path");
assert!(
db_path.ends_with("rtk/history.db"),
"expected default path ending with rtk/history.db, got: {}",
db_path.display()
);
temp_env::with_var_unset("RTK_DB_PATH", || {
let db_path = get_db_path().expect("Failed to get db path");
assert!(
db_path.ends_with("rtk/history.db"),
"expected default path ending with rtk/history.db, got: {}",
db_path.display()
);
});
}
// 8b. Tracker::new() gates schema migration behind PRAGMA user_version, so a
@@ -2140,24 +2136,22 @@ mod tests {
env::temp_dir().join(format!("rtk_test_schema_version_{}.db", std::process::id()));
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
env::set_var("RTK_DB_PATH", &db_path);
temp_env::with_var("RTK_DB_PATH", Some(&db_path), || {
let tracker = Tracker::new().expect("first open should run migrations");
let version: i64 = tracker
.conn
.query_row("PRAGMA user_version", [], |row| row.get(0))
.expect("user_version should be readable");
assert_eq!(version, SCHEMA_VERSION);
drop(tracker);
let tracker = Tracker::new().expect("first open should run migrations");
let version: i64 = tracker
.conn
.query_row("PRAGMA user_version", [], |row| row.get(0))
.expect("user_version should be readable");
assert_eq!(version, SCHEMA_VERSION);
drop(tracker);
// Second open on the same file must skip migrations without erroring, and
// the DB must still be fully usable (tables from the first open persist).
let tracker2 = Tracker::new().expect("second open should skip migrations cleanly");
tracker2
.record("git status", "rtk git status", 100, 20, 50)
.expect("commands table should already exist and accept writes");
env::remove_var("RTK_DB_PATH");
// Second open on the same file must skip migrations without erroring, and
// the DB must still be fully usable (tables from the first open persist).
let tracker2 = Tracker::new().expect("second open should skip migrations cleanly");
tracker2
.record("git status", "rtk git status", 100, 20, 50)
.expect("commands table should already exist and accept writes");
});
// nosemgrep: filesystem-deletion -- test-only cleanup of this test's own throwaway temp DB file, not production/user data.
let _ = std::fs::remove_file(&db_path);
}
+3 -2
View File
@@ -2264,8 +2264,9 @@ mod tests {
#[test]
fn test_audit_log_silent_when_disabled() {
std::env::remove_var("RTK_HOOK_AUDIT");
audit_log("test", "git status", "rtk git status");
temp_env::with_var_unset("RTK_HOOK_AUDIT", || {
audit_log("test", "git status", "rtk git status");
});
}
#[test]
+59 -87
View File
@@ -5276,70 +5276,72 @@ fn show_claude_config() -> Result<()> {
}
// Check OpenCode plugin
if let Ok(opencode_dir) = resolve_opencode_dir() {
let plugin = opencode_plugin_path(&opencode_dir);
if plugin.exists() {
println!("[ok] OpenCode: plugin installed ({})", plugin.display());
} else {
println!("[--] OpenCode: plugin not found");
match resolve_opencode_dir() {
Ok(opencode_dir) => {
let plugin = opencode_plugin_path(&opencode_dir);
if plugin.exists() {
println!("[ok] OpenCode: plugin installed ({})", plugin.display());
} else {
println!("[--] OpenCode: plugin not found");
}
}
} else {
println!("[--] OpenCode: config dir not found");
_ => println!("[--] OpenCode: config dir not found"),
}
// Check Cursor hooks
if let Ok(cursor_dir) = resolve_cursor_dir() {
let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE);
let cursor_hooks_json = cursor_dir.join(HOOKS_JSON);
match resolve_cursor_dir() {
Ok(cursor_dir) => {
let cursor_hook = cursor_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE);
let cursor_hooks_json = cursor_dir.join(HOOKS_JSON);
// Check for binary command in hooks.json first
let cursor_binary_registered = if cursor_hooks_json.exists() {
let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default();
if let Ok(root) = from_json_str::<serde_json::Value>(&content) {
cursor_hook_already_present(&root)
// Check for binary command in hooks.json first
let cursor_binary_registered = if cursor_hooks_json.exists() {
let content = fs::read_to_string(&cursor_hooks_json).unwrap_or_default();
if let Ok(root) = from_json_str::<serde_json::Value>(&content) {
cursor_hook_already_present(&root)
} else {
false
}
} else {
false
}
} else {
false
};
};
if cursor_binary_registered {
println!("[ok] Cursor hook: registered in hooks.json");
} else if cursor_hook.exists() {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let meta = fs::metadata(&cursor_hook)?;
let is_executable = meta.permissions().mode() & 0o111 != 0;
let content = fs::read_to_string(&cursor_hook)?;
let _is_thin = content.contains("rtk rewrite");
if cursor_binary_registered {
println!("[ok] Cursor hook: registered in hooks.json");
} else if cursor_hook.exists() {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let meta = fs::metadata(&cursor_hook)?;
let is_executable = meta.permissions().mode() & 0o111 != 0;
let content = fs::read_to_string(&cursor_hook)?;
let _is_thin = content.contains("rtk rewrite");
if !is_executable {
println!(
"[warn] Cursor hook: {} (legacy script, NOT executable)",
cursor_hook.display()
);
} else {
if !is_executable {
println!(
"[warn] Cursor hook: {} (legacy script, NOT executable)",
cursor_hook.display()
);
} else {
println!(
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
cursor_hook.display()
);
}
}
#[cfg(not(unix))]
{
println!(
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
cursor_hook.display()
);
}
} else {
println!("[--] Cursor hook: not found");
}
#[cfg(not(unix))]
{
println!(
"[warn] Cursor hook: {} (legacy script — run `rtk init -g --agent cursor` to upgrade)",
cursor_hook.display()
);
}
} else {
println!("[--] Cursor hook: not found");
}
} else {
println!("[--] Cursor: home dir not found");
_ => println!("[--] Cursor: home dir not found"),
}
println!("\nUsage:");
@@ -9026,52 +9028,30 @@ mod tests {
}
use std::sync::Mutex;
static CLAUDE_DIR_LOCK: Mutex<()> = Mutex::new(());
static PI_DIR_LOCK: Mutex<()> = Mutex::new(());
/// Serialises all tests that mutate the process-wide working directory.
static CWD_LOCK: Mutex<()> = Mutex::new(());
fn with_claude_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
let _guard = CLAUDE_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let claude_dir = tmp.path().join(CLAUDE_DIR);
fs::create_dir_all(&claude_dir).unwrap();
let orig = std::env::var_os("CLAUDE_CONFIG_DIR");
std::env::set_var("CLAUDE_CONFIG_DIR", &claude_dir);
f(&claude_dir);
match orig {
Some(v) => std::env::set_var("CLAUDE_CONFIG_DIR", v),
None => std::env::remove_var("CLAUDE_CONFIG_DIR"),
}
temp_env::with_var("CLAUDE_CONFIG_DIR", Some(&claude_dir), || f(&claude_dir));
}
fn with_pi_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let pi_dir = tmp.path().join("pi_agent");
fs::create_dir_all(&pi_dir).unwrap();
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &pi_dir);
f(&pi_dir);
match orig {
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
}
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&pi_dir), || f(&pi_dir));
}
// OMP reuses PI_CODING_AGENT_DIR, so this overrides the same variable as
// with_pi_dir_override; temp-env serialises the two against each other.
fn with_omp_dir_override<F: FnOnce(&Path)>(tmp: &TempDir, f: F) {
// OMP reuses PI_CODING_AGENT_DIR, so share the Pi environment lock.
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let omp_dir = tmp.path().join("omp_agent");
fs::create_dir_all(&omp_dir).unwrap();
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &omp_dir);
f(&omp_dir);
match orig {
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
}
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&omp_dir), || f(&omp_dir));
}
#[test]
@@ -9604,18 +9584,10 @@ mod tests {
fn test_run_pi_mode_global_creates_plugin_when_dir_absent() {
let tmp = TempDir::new().unwrap();
let absent_dir = tmp.path().join("no_such_pi_dir");
let _guard = PI_DIR_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let orig = std::env::var_os(PI_CODING_AGENT_DIR_ENV);
std::env::set_var(PI_CODING_AGENT_DIR_ENV, &absent_dir);
let result = run_pi_mode(true, InitContext::default());
match orig {
Some(v) => std::env::set_var(PI_CODING_AGENT_DIR_ENV, v),
None => std::env::remove_var(PI_CODING_AGENT_DIR_ENV),
}
result.unwrap();
temp_env::with_var(PI_CODING_AGENT_DIR_ENV, Some(&absent_dir), || {
run_pi_mode(true, InitContext::default())
})
.unwrap();
let plugin = absent_dir.join(PI_EXTENSIONS_SUBDIR).join(PI_PLUGIN_FILE);
assert!(
+8 -11
View File
@@ -403,8 +403,7 @@ mod tests {
/// Overrides the store path via a scoped env var (not possible with
/// the real function), so we test the logic by calling internal fns.
fn setup_test_env(temp: &TempDir) -> PathBuf {
let store_file = temp.path().join("trusted_filters.json");
store_file
temp.path().join("trusted_filters.json")
}
fn check_trust_with_store(filter_path: &Path, store_file: &Path) -> Result<TrustStatus> {
@@ -565,15 +564,13 @@ mod tests {
std::fs::write(&filter, "[filters.test]\nmatch_command = \"echo\"").unwrap();
// Both env vars must be set: trust override + CI indicator
#[allow(deprecated)]
std::env::set_var("RTK_TRUST_PROJECT_FILTERS", "1");
#[allow(deprecated)]
std::env::set_var("CI", "true");
let status = check_trust(&filter).unwrap();
#[allow(deprecated)]
std::env::remove_var("RTK_TRUST_PROJECT_FILTERS");
#[allow(deprecated)]
std::env::remove_var("CI");
let status = temp_env::with_vars(
[
("RTK_TRUST_PROJECT_FILTERS", Some("1")),
("CI", Some("true")),
],
|| check_trust(&filter).unwrap(),
);
assert_eq!(status, TrustStatus::EnvOverride);
}
+11 -4
View File
@@ -1,3 +1,4 @@
#![allow(clippy::collapsible_if)]
mod analytics;
mod cmds;
mod core;
@@ -2960,11 +2961,17 @@ fn run_cli() -> Result<i32> {
unsafe extern "C" fn handle_signal(sig: libc::c_int) {
let pid = PROXY_CHILD_PID.load(Ordering::SeqCst);
if pid != 0 {
libc::kill(pid as libc::pid_t, libc::SIGTERM);
libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0);
// nosemgrep: unsafe-block
unsafe {
libc::kill(pid as libc::pid_t, libc::SIGTERM);
libc::waitpid(pid as libc::pid_t, std::ptr::null_mut(), 0);
}
}
// nosemgrep: unsafe-block
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
// nosemgrep: unsafe-block
unsafe {