mirror of
https://github.com/instructkr/claw-code.git
synced 2026-10-17 00:00:05 +08:00
fix: restore workspace unsafe_code=forbid, fix TaskClaim TOCTOU, remove TUI deps
Review fixes for PR #3250: 1. Restore workspace-level unsafe_code = "forbid" (was downgraded to "warn" for TUI support). The per-crate [lints.rust] unsafe_code = "allow" on rusty-claude-cli already overrides it where needed. 2. Fix TaskClaim TOCTOU race in claim_task(). Replace exists()-then-write with OpenOptions::create_new(true) which atomically fails if the lock file already exists, eliminating the race window between the check and the write. 3. Remove TUI scaffolding deps (ratatui, tui-textarea, gag, crossbeam-channel, libc, unicode-width) and the /tui slash command spec — these belong in a future TUI PR and are unused in the current source. Also updates the provider chain precedence test to reflect the new behavior where the caller's resolved model is the chain primary. Co-Authored-By: Claw <noreply@openclaw.ai>
This commit is contained in:
1 parent
fe71ead55f
commit
08cce855b5
5 files changed
+653
-892
No files matched your search
Generated
+630
-856
File diff suppressed because it is too large.
Load diff
+1
-3
@@ -12,9 +12,7 @@ publish = false
|
||||
serde_json = "1"
|
||||
|
||||
[workspace.lints.rust]
|
||||
# rusty-claude-cli needs unsafe for TUI stdout suppression (dup/dup2),
|
||||
# all other crates should avoid it — override per-crate if needed.
|
||||
unsafe_code = "warn"
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
|
||||
@@ -65,13 +65,6 @@ const SLASH_COMMAND_SPECS: &[SlashCommandSpec] = &[
|
||||
argument_hint: None,
|
||||
resume_supported: true,
|
||||
},
|
||||
SlashCommandSpec {
|
||||
name: "tui",
|
||||
aliases: &[],
|
||||
summary: "Switch to the split-pane TUI dashboard",
|
||||
argument_hint: None,
|
||||
resume_supported: true,
|
||||
},
|
||||
SlashCommandSpec {
|
||||
name: "status",
|
||||
aliases: &[],
|
||||
@@ -6045,7 +6038,7 @@ mod tests {
|
||||
assert!(!help.contains("/login"));
|
||||
assert!(!help.contains("/logout"));
|
||||
assert!(help.contains("/setup"));
|
||||
assert_eq!(slash_command_specs().len(), 141);
|
||||
assert_eq!(slash_command_specs().len(), 140);
|
||||
assert!(resume_supported_slash_commands().len() >= 39);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,9 @@ path = "src/main.rs"
|
||||
api = { path = "../api" }
|
||||
commands = { path = "../commands" }
|
||||
crossterm = "0.28"
|
||||
crossbeam-channel = "0.5"
|
||||
gag = "1"
|
||||
pulldown-cmark = "0.13"
|
||||
once_cell = "1"
|
||||
ratatui = "0.29"
|
||||
rustyline = "15"
|
||||
tui-textarea = "0.7"
|
||||
runtime = { path = "../runtime" }
|
||||
plugins = { path = "../plugins" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -28,8 +24,6 @@ syntect = "5"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "signal", "time"] }
|
||||
tools = { path = "../tools" }
|
||||
log = "0.4"
|
||||
libc = "0.2"
|
||||
unicode-width = "0.2"
|
||||
|
||||
|
||||
[lints.rust]
|
||||
|
||||
@@ -2826,9 +2826,6 @@ fn claim_task(task_id: &str, agent_id: &str, team_id: &str) -> Result<bool, Stri
|
||||
let dir = claims_dir();
|
||||
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||
let lock_path = dir.join(format!("{task_id}.lock"));
|
||||
if lock_path.exists() {
|
||||
return Ok(false);
|
||||
}
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
@@ -2839,20 +2836,22 @@ fn claim_task(task_id: &str, agent_id: &str, team_id: &str) -> Result<bool, Stri
|
||||
"team_id": team_id,
|
||||
"claimed_at": ts,
|
||||
});
|
||||
// Atomic claim: write to temp file then rename
|
||||
let tmp_path = dir.join(format!("{task_id}.lock.tmp.{agent_id}"));
|
||||
std::fs::write(
|
||||
&tmp_path,
|
||||
serde_json::to_string_pretty(&entry).map_err(|e| e.to_string())?,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
match std::fs::rename(&tmp_path, &lock_path) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(_) => {
|
||||
// Another agent claimed it first
|
||||
let _ = std::fs::remove_file(&tmp_path);
|
||||
Ok(false)
|
||||
// Atomic claim: create_new fails atomically if the file already exists,
|
||||
// eliminating the TOCTOU race between exists() and write().
|
||||
let content = serde_json::to_string_pretty(&entry).map_err(|e| e.to_string())?;
|
||||
match std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&lock_path)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
use std::io::Write;
|
||||
file.write_all(content.as_bytes())
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(true)
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(false),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12490,9 +12489,12 @@ printf 'pwsh:%s' "$1"
|
||||
.expect("chain with primary override should construct");
|
||||
|
||||
// then
|
||||
assert_eq!(client.chain.len(), 2);
|
||||
assert_eq!(client.chain[0].model, "grok-3");
|
||||
assert_eq!(client.chain[1].model, "claude-sonnet-4-6");
|
||||
// The caller's model is now the primary; fallbacks are appended
|
||||
// as recovery entries, deduped.
|
||||
assert_eq!(client.chain.len(), 3);
|
||||
assert_eq!(client.chain[0].model, "claude-haiku-4-5-20251213");
|
||||
assert_eq!(client.chain[1].model, "grok-3");
|
||||
assert_eq!(client.chain[2].model, "claude-sonnet-4-6");
|
||||
|
||||
match original_anthropic {
|
||||
Some(value) => std::env::set_var("ANTHROPIC_API_KEY", value),
|
||||
|
||||
Reference in new issue
Block a user