ci: switch clippy to pinned nightly, clean up workspace lints

- rust.yml: move clippy to nightly-2026-04-14 (was stable); also pin
  fmt to the same nightly date for consistency. Both jobs now use the
  dated nightly and the run-step uses `cargo +nightly-2026-04-14 ...`.
- AGENTS.md: update developer commands to match CI.
- Duration constructors: replace `Duration::from_secs(N * 60)` /
  `Duration::from_millis(N * 1000)` with `from_mins` / `from_secs` /
  `from_hours` across the workspace to satisfy clippy's new
  `duration_suboptimal_units` lint. std::time::Duration only — custom
  `settings::duration::Duration` sites kept on `from_secs`.
- map/unwrap_or cleanup: `.map(f).unwrap_or(v)` → `.map_or(v, f)`,
  `.map(f).unwrap_or(false)` on Result → `.is_ok_and(f)`, per
  `clippy::map_unwrap_or`.
- Misc lints: collapse nested `if` into match guard in
  handler/llm/api.rs and run_state.rs; replace `columns.len() > 0`
  with `!columns.is_empty()`; switch a pair of `sort_by` calls to
  `sort_by_key`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-16 18:59:11 -04:00
parent eb596c057d
commit 1048534e2c
No known key found for this signature in database
41 changed files with 95 additions and 116 deletions

View file

@ -45,9 +45,9 @@ jobs:
persist-credentials: false
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
toolchain: nightly
toolchain: nightly-2026-04-14
components: rustfmt
- run: cargo +nightly fmt --check --all
- run: cargo +nightly-2026-04-14 fmt --check --all
clippy:
name: Clippy
@ -57,10 +57,13 @@ jobs:
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
toolchain: nightly-2026-04-14
components: clippy
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
cache-on-failure: true
- run: cargo clippy --workspace --all-targets -- -D warnings
- run: cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings
test:
name: Test (Linux)

View file

@ -11,9 +11,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `cargo nextest run -p fabro-workflow -- test_name` — run a single test
- `set -a && source .env && set +a && cargo nextest run --workspace --profile e2e --run-ignored only` — run all E2E live tests (requires credentials in `.env`, see `.env.example`)
- `set -a && source .env && set +a && cargo nextest run -p fabro-llm --profile e2e --run-ignored only` — run E2E tests for a single crate
- `cargo +nightly fmt --check --all` — check formatting (nightly required for rustfmt config)
- `cargo +nightly fmt --all` — auto-format
- `cargo clippy --workspace -- -D warnings` — lint
- `cargo +nightly-2026-04-14 fmt --check --all` — check formatting (pinned nightly required for rustfmt config; CI uses the same date)
- `cargo +nightly-2026-04-14 fmt --all` — auto-format
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings` — lint (CI runs nightly clippy to match; install with `rustup toolchain install nightly-2026-04-14 --profile minimal --component clippy,rustfmt`)
macOS note: if `cargo nextest run` fails with `Too many open files (os error 24)` / `EMFILE`, raise the shell's soft FD limit before running tests, for example `ulimit -n 4096 && cargo nextest run --workspace`. Some terminals and inherited agent sessions start with `ulimit -n 256`, which is too low for the shared CLI test daemon under parallel nextest load.

View file

@ -14,7 +14,7 @@ pub fn make_mcp_tools(manager: &Arc<McpConnectionManager>) -> Vec<RegisteredTool
.map(|(qualified_name, info)| {
let mgr = Arc::clone(manager);
let name = qualified_name.clone();
let tool_timeout = std::time::Duration::from_secs(120);
let tool_timeout = std::time::Duration::from_mins(2);
RegisteredTool {
definition: ToolDefinition {

View file

@ -318,8 +318,7 @@ async fn detect_binary_on_path(binary: &str) -> bool {
.stderr(Stdio::null())
.status()
.await
.map(|s| s.success())
.unwrap_or(false)
.is_ok_and(|s| s.success())
}
// ---------------------------------------------------------------------------

View file

@ -75,13 +75,11 @@ pub(crate) async fn attach_run_with_client(
let state = client.get_run_state(run_id).await?;
let auto_approve = state.run.as_ref().is_some_and(|record| {
fabro_config::resolve_run_from_file(&record.settings)
.map(|settings| settings.execution.approval == ApprovalMode::Auto)
.unwrap_or(false)
.is_ok_and(|settings| settings.execution.approval == ApprovalMode::Auto)
});
let verbose = state.run.as_ref().is_some_and(|record| {
fabro_config::resolve_cli_from_file(&record.settings)
.map(|settings| settings.output.verbosity == OutputVerbosity::Verbose)
.unwrap_or(false)
.is_ok_and(|settings| settings.output.verbosity == OutputVerbosity::Verbose)
});
let events = client.list_run_events(run_id, None, None).await?;
let replay_events = events.clone();

View file

@ -294,8 +294,7 @@ mod tests {
let status = match std::fs::read_to_string(std::path::Path::new("/nonexistent/status.json"))
{
Ok(data) => serde_json::from_str::<RunStatusRecord>(&data)
.map(|record| record.status)
.unwrap_or(RunStatus::Dead),
.map_or(RunStatus::Dead, |record| record.status),
Err(_) => RunStatus::Dead,
};
assert_eq!(status, RunStatus::Dead);

View file

@ -94,7 +94,7 @@ fn dir_size(path: &Path) -> u64 {
if ft.is_dir() {
total += dir_size(&entry.path());
} else {
total += entry.metadata().map(|m| m.len()).unwrap_or(0);
total += entry.metadata().map_or(0, |m| m.len());
}
}
}

View file

@ -264,7 +264,7 @@ fn exec_creates_file() {
"claude-haiku-4-5",
"Create a file called hello.txt containing exactly 'Hello'",
])
.timeout(std::time::Duration::from_secs(120))
.timeout(std::time::Duration::from_mins(2))
.assert()
.success();
let path = context.temp_dir.join("hello.txt");
@ -291,7 +291,7 @@ fn exec_shell_command() {
"claude-haiku-4-5",
"Run the shell command `echo arc_test_marker_42` and tell me what it printed",
])
.timeout(std::time::Duration::from_secs(120))
.timeout(std::time::Duration::from_mins(2))
.assert()
.success();
}
@ -311,7 +311,7 @@ fn exec_read_only_blocks_write() {
"claude-haiku-4-5",
"Create a file called forbidden.txt containing 'should not exist'",
])
.timeout(std::time::Duration::from_secs(120))
.timeout(std::time::Duration::from_mins(2))
.assert()
.success();
assert!(
@ -337,7 +337,7 @@ fn exec_json_output_format() {
"claude-haiku-4-5",
"Create a file called test.txt containing 'test'",
])
.timeout(std::time::Duration::from_secs(120))
.timeout(std::time::Duration::from_mins(2))
.assert()
.success()
.get_output()
@ -372,7 +372,7 @@ fn exec_read_and_edit() {
"claude-haiku-4-5",
"Read data.txt then replace its entire content with 'new content'",
])
.timeout(std::time::Duration::from_secs(120))
.timeout(std::time::Duration::from_mins(2))
.assert()
.success();
let content =

View file

@ -17,7 +17,7 @@ fn test_exec_creates_file() {
"claude-haiku-4-5",
"Create a file called hello.txt containing exactly 'Hello from exec scenario'",
]);
cmd.timeout(Duration::from_secs(120));
cmd.timeout(Duration::from_mins(2));
cmd.assert().success();
let hello = context.temp_dir.join("hello.txt");

View file

@ -66,7 +66,7 @@ pub(super) fn run_state(run_dir: &Path) -> RunProjection {
pub(super) fn timeout_for(sandbox: &str) -> Duration {
match sandbox {
"daytona" => Duration::from_secs(600),
_ => Duration::from_secs(180),
"daytona" => Duration::from_mins(10),
_ => Duration::from_mins(3),
}
}

View file

@ -167,7 +167,7 @@ pub(super) use sandbox_tests;
pub(super) fn timeout_for(sandbox: &str) -> Duration {
match sandbox {
"daytona" => Duration::from_secs(600),
_ => Duration::from_secs(180),
"daytona" => Duration::from_mins(10),
_ => Duration::from_mins(3),
}
}

View file

@ -67,7 +67,7 @@ pub fn write_env_file(path: &Path, entries: &HashMap<String, String>) -> io::Res
let tmp_path = parent.join(format!(".{file_name}.tmp-{}", ulid::Ulid::new()));
let mut data = entries.iter().collect::<Vec<_>>();
data.sort_by(|(left, _), (right, _)| left.cmp(right));
data.sort_by_key(|(left, _)| *left);
let contents = data
.into_iter()
.map(|(key, value)| format!("{key}={}", encode_value(value)))

View file

@ -1281,7 +1281,7 @@ mod tests {
backoff: BackoffPolicy {
initial_delay: Duration::from_secs(5),
factor: 2.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: false,
},
}),
@ -2049,9 +2049,9 @@ mod tests {
RetryPolicy {
max_attempts: 3,
backoff: BackoffPolicy {
initial_delay: Duration::from_secs(60),
initial_delay: Duration::from_mins(1),
factor: 1.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: false,
},
}

View file

@ -283,7 +283,7 @@ mod tests {
fn convert_ast_duration_str() {
assert_eq!(
convert_value(&AstValue::Str("900s".into())),
AttrValue::Duration(Duration::from_secs(900))
AttrValue::Duration(Duration::from_mins(15))
);
assert_eq!(
convert_value(&AstValue::Str("250ms".into())),
@ -291,15 +291,15 @@ mod tests {
);
assert_eq!(
convert_value(&AstValue::Str("15m".into())),
AttrValue::Duration(Duration::from_secs(900))
AttrValue::Duration(Duration::from_mins(15))
);
assert_eq!(
convert_value(&AstValue::Str("2h".into())),
AttrValue::Duration(Duration::from_secs(7200))
AttrValue::Duration(Duration::from_hours(2))
);
assert_eq!(
convert_value(&AstValue::Str("1d".into())),
AttrValue::Duration(Duration::from_secs(86400))
AttrValue::Duration(Duration::from_hours(24))
);
}
@ -413,7 +413,7 @@ mod tests {
);
assert_eq!(
plan.attrs.get("timeout").and_then(AttrValue::as_duration),
Some(Duration::from_secs(900))
Some(Duration::from_mins(15))
);
let implement = &graph.nodes["implement"];
@ -422,7 +422,7 @@ mod tests {
.attrs
.get("timeout")
.and_then(AttrValue::as_duration),
Some(Duration::from_secs(1800))
Some(Duration::from_mins(30))
);
}

View file

@ -236,7 +236,7 @@ impl Interviewer for ConsoleInterviewer {
// Non-TTY fallback: line-based stdin reading
let s = self.styles;
eprintln!("{} {}", s.bold_cyan.apply_to("?"), question.text,);
eprintln!("{} {}", s.bold_cyan.apply_to("?"), question.text);
match question.question_type {
QuestionType::MultipleChoice | QuestionType::MultiSelect => {

View file

@ -357,7 +357,7 @@ mod tests {
#[async_trait]
impl Interviewer for SlowInterviewer {
async fn ask(&self, _question: Question) -> Answer {
time::sleep(std::time::Duration::from_secs(60)).await;
time::sleep(std::time::Duration::from_mins(1)).await;
Answer::yes()
}
}

View file

@ -80,7 +80,7 @@ mod tests {
BackoffPolicy {
initial_delay: Duration::from_micros(1),
factor: 2.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: false,
}
}
@ -249,7 +249,7 @@ mod tests {
backoff: BackoffPolicy {
initial_delay: Duration::from_secs(10), // high, but retry_after is low
factor: 2.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: false,
},
..Default::default()

View file

@ -724,7 +724,7 @@ impl Default for RetryPolicy {
backoff: BackoffPolicy {
initial_delay: std::time::Duration::from_secs(1),
factor: 2.0,
max_delay: std::time::Duration::from_secs(60),
max_delay: std::time::Duration::from_mins(1),
jitter: true,
},
on_retry: None,
@ -1112,7 +1112,7 @@ mod tests {
backoff: BackoffPolicy {
initial_delay: Duration::from_secs(1),
factor: 2.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: false,
},
..Default::default()
@ -1148,7 +1148,7 @@ mod tests {
backoff: BackoffPolicy {
initial_delay: Duration::from_secs(1),
factor: 2.0,
max_delay: Duration::from_secs(60),
max_delay: Duration::from_mins(1),
jitter: true,
},
..Default::default()

View file

@ -177,7 +177,7 @@ pub async fn run_retro_agent(
let config = SessionOptions {
max_tool_rounds_per_input: 20,
wall_clock_timeout: Some(Duration::from_secs(180)),
wall_clock_timeout: Some(Duration::from_mins(3)),
// Disable features not needed for retro analysis
enable_context_compaction: false,
skill_dirs: Some(vec![]),

View file

@ -281,7 +281,7 @@ impl DaytonaSandbox {
use daytona_api_client::models::SnapshotState;
let mut delay = std::time::Duration::from_secs(2);
let max_delay = std::time::Duration::from_secs(30);
let deadline = Instant::now() + std::time::Duration::from_secs(600);
let deadline = Instant::now() + std::time::Duration::from_mins(10);
while Instant::now() < deadline {
time::sleep(delay).await;

View file

@ -687,8 +687,7 @@ impl AppState {
fn issue_artifact_upload_token(&self, run_id: &RunId) -> Result<String, ApiError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or(0);
.map_or(0, |duration| duration.as_secs());
let claims = ArtifactUploadClaims {
iss: ARTIFACT_UPLOAD_TOKEN_ISSUER.to_string(),
iat: now,
@ -1242,12 +1241,9 @@ async fn get_system_info(
}
fn system_features(settings: &SettingsLayer) -> SystemFeatures {
let session_sandboxes = fabro_config::resolve_features_from_file(settings)
.map(|s| s.session_sandboxes)
.unwrap_or(false);
let retros = fabro_config::resolve_run_from_file(settings)
.map(|s| s.execution.retros)
.unwrap_or(false);
let session_sandboxes =
fabro_config::resolve_features_from_file(settings).is_ok_and(|s| s.session_sandboxes);
let retros = fabro_config::resolve_run_from_file(settings).is_ok_and(|s| s.execution.retros);
SystemFeatures {
session_sandboxes: Some(session_sandboxes),
retros: Some(retros),
@ -9238,7 +9234,7 @@ timeout = "30s"
// Verify columns are included in the response
let columns = body["columns"].as_array().expect("columns should be array");
assert!(columns.len() > 0);
assert!(!columns.is_empty());
assert!(columns.iter().any(|c| c["id"].as_str() == Some("waiting")));
assert!(
columns

View file

@ -218,8 +218,7 @@ fn session_cookie_secure(state: &AppState) -> bool {
.web
.url
.resolve(|name| std::env::var(name).ok())
.map(|resolved| resolved.value.starts_with("https://"))
.unwrap_or(false)
.is_ok_and(|resolved| resolved.value.starts_with("https://"))
}
async fn login_dev_token(

View file

@ -223,20 +223,14 @@ impl RunProjection {
started_at: Some(ts),
});
}
EventBody::InterviewCompleted(props) => {
if !props.question_id.is_empty() {
self.pending_interviews.remove(&props.question_id);
}
EventBody::InterviewCompleted(props) if !props.question_id.is_empty() => {
self.pending_interviews.remove(&props.question_id);
}
EventBody::InterviewTimeout(props) => {
if !props.question_id.is_empty() {
self.pending_interviews.remove(&props.question_id);
}
EventBody::InterviewTimeout(props) if !props.question_id.is_empty() => {
self.pending_interviews.remove(&props.question_id);
}
EventBody::InterviewInterrupted(props) => {
if !props.question_id.is_empty() {
self.pending_interviews.remove(&props.question_id);
}
EventBody::InterviewInterrupted(props) if !props.question_id.is_empty() => {
self.pending_interviews.remove(&props.question_id);
}
EventBody::StagePrompt(props) => {
let Some(node_id) = stored.node_id.as_deref() else {

View file

@ -170,7 +170,7 @@ impl Database {
}
summaries.push(RunDatabase::build_summary(&db, &run_id).await?);
}
summaries.sort_by(|a, b| b.run_id.created_at().cmp(&a.run_id.created_at()));
summaries.sort_by_key(|b| std::cmp::Reverse(b.run_id.created_at()));
Ok(summaries)
}

View file

@ -13,7 +13,7 @@ impl Default for BufferPolicy {
fn default() -> Self {
Self {
count_threshold: 20,
time_threshold: Duration::from_secs(60),
time_threshold: Duration::from_mins(1),
}
}
}
@ -98,7 +98,7 @@ mod tests {
&rx,
BufferPolicy {
count_threshold: 2,
time_threshold: Duration::from_secs(60),
time_threshold: Duration::from_mins(1),
},
move |tracks| {
let events: Vec<String> = tracks.iter().map(|t| t.event.clone()).collect();
@ -133,7 +133,7 @@ mod tests {
&rx,
BufferPolicy {
count_threshold: 2,
time_threshold: Duration::from_secs(60),
time_threshold: Duration::from_mins(1),
},
move |_| {
*mid.lock().unwrap() = true;
@ -210,7 +210,7 @@ mod tests {
&rx,
BufferPolicy {
count_threshold: 100, // won't trigger
time_threshold: Duration::from_secs(60),
time_threshold: Duration::from_mins(1),
},
move |tracks| {
let events: Vec<String> = tracks.iter().map(|t| t.event.clone()).collect();

View file

@ -451,7 +451,7 @@ impl Graph {
{
Some(d) if d.is_zero() => None,
Some(d) => Some(d),
None => Some(Duration::from_secs(1800)),
None => Some(Duration::from_mins(30)),
}
}
@ -763,7 +763,7 @@ mod tests {
#[test]
fn graph_stall_timeout_default() {
let g = Graph::new("empty");
assert_eq!(g.stall_timeout(), Some(Duration::from_secs(1800)));
assert_eq!(g.stall_timeout(), Some(Duration::from_mins(30)));
}
#[test]

View file

@ -87,12 +87,12 @@ mod tests {
#[test]
fn delay_with_jitter_within_range() {
let b = BackoffPolicy {
initial_delay: Duration::from_millis(1000),
initial_delay: Duration::from_secs(1),
factor: 1.0,
max_delay: Duration::from_secs(10),
jitter: true,
};
let base = Duration::from_millis(1000);
let base = Duration::from_secs(1);
let min = base.mul_f64(0.5);
let max = base.mul_f64(1.5);

View file

@ -130,9 +130,7 @@ fn eval_clause(clause: &Clause, outcome: &Outcome, context: &Context) -> bool {
Op::Matches => {
let resolved = resolve_key(&clause.key, outcome, context);
// Regex was validated at parse time, so unwrap is safe
regex::Regex::new(&clause.value)
.map(|re| re.is_match(&resolved))
.unwrap_or(false)
regex::Regex::new(&clause.value).is_ok_and(|re| re.is_match(&resolved))
}
}
}

View file

@ -2771,7 +2771,7 @@ pub struct Emitter {
impl std::fmt::Debug for Emitter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let count = self.listeners.lock().map(|l| l.len()).unwrap_or(0);
let count = self.listeners.lock().map_or(0, |l| l.len());
f.debug_struct("Emitter")
.field("run_id", &self.run_id)
.field("listener_count", &count)

View file

@ -397,7 +397,7 @@ mod tests {
);
node.attrs.insert(
"timeout".to_string(),
AttrValue::Duration(Duration::from_millis(5000)),
AttrValue::Duration(Duration::from_secs(5)),
);
let context = Context::new();
let graph = Graph::new("test");

View file

@ -110,11 +110,9 @@ fn track_file_event(event: &AgentEvent, state: &mut FileTracking) {
tool_name,
tool_call_id,
arguments,
} => {
if tool_name == "write_file" || tool_name == "edit_file" {
if let Some(path) = arguments.get("file_path").and_then(|v| v.as_str()) {
state.pending.insert(tool_call_id.clone(), path.to_string());
}
} if tool_name == "write_file" || tool_name == "edit_file" => {
if let Some(path) = arguments.get("file_path").and_then(|v| v.as_str()) {
state.pending.insert(tool_call_id.clone(), path.to_string());
}
}
AgentEvent::ToolCallCompleted {

View file

@ -787,7 +787,7 @@ mod tests {
#[test]
fn parse_duration_str_minutes() {
assert_eq!(parse_duration_str("5m"), Duration::from_secs(300));
assert_eq!(parse_duration_str("5m"), Duration::from_mins(5));
}
#[test]

View file

@ -85,8 +85,7 @@ impl RunLifecycle<WorkflowGraph> for ArtifactLifecycle {
// Record epoch seconds (floored to integer for macOS stat mtime parity)
let epoch = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as f64)
.unwrap_or(0.0);
.map_or(0.0, |d| d.as_secs() as f64);
*self.attempt_start_epoch.lock().unwrap() = Some(epoch);
Ok(NodeDecision::Continue)
}

View file

@ -75,8 +75,7 @@ pub async fn create(store: &Database, request: CreateRunInput) -> Result<Created
.map_err(|err| Error::Parse(err.to_string()))?;
if fabro_config::resolve_run_from_file(&resolved.settings)
.map(|settings| settings.execution.mode != RunMode::DryRun)
.unwrap_or(true)
.map_or(true, |settings| settings.execution.mode != RunMode::DryRun)
{
validate_sandbox_provider(&resolved.settings)?;
}

View file

@ -297,8 +297,7 @@ impl RunSession {
accepted_definition.map(|definition| Arc::new(definition.workflow_bundle()));
let (origin_url, detected_base_branch) = detect_repo_info(&working_directory)
.map(|(url, branch)| (Some(url), branch))
.unwrap_or((None, None));
.map_or((None, None), |(url, branch)| (Some(url), branch));
let resolved = fabro_config::resolve_run_from_file(settings)
.map_err(|errors| Error::Precondition(render_resolve_errors(&errors)))?;

View file

@ -396,7 +396,7 @@ async fn resolve_devcontainer(options: &mut InitOptions) -> Result<(), Error> {
.sandbox
.apply_devcontainer_snapshot(devcontainer_to_snapshot_config(&config));
let timeout = std::time::Duration::from_millis(300_000);
let timeout = std::time::Duration::from_mins(5);
for command in &config.initialize_commands {
let shell_commands = match command {
fabro_devcontainer::Command::Shell(shell) => vec![shell.clone()],

View file

@ -4,9 +4,9 @@ use fabro_core::retry::{BackoffPolicy, RetryPolicy};
use fabro_graphviz::graph::types::{Graph as GvGraph, Node as GvNode};
const DEFAULT_BACKOFF: BackoffPolicy = BackoffPolicy {
initial_delay: Duration::from_millis(5_000),
initial_delay: Duration::from_secs(5),
factor: 2.0,
max_delay: Duration::from_millis(60_000),
max_delay: Duration::from_mins(1),
jitter: true,
};
@ -59,7 +59,7 @@ fn preset_retry_policy(preset: &str) -> Option<RetryPolicy> {
"patient" => Some(RetryPolicy {
max_attempts: 3,
backoff: BackoffPolicy {
initial_delay: Duration::from_millis(2_000),
initial_delay: Duration::from_secs(2),
factor: 3.0,
..DEFAULT_BACKOFF
},
@ -126,7 +126,7 @@ mod tests {
let graph = Graph::new("test");
let policy = build_retry_policy(&node, &graph);
assert_eq!(policy.max_attempts, 4);
assert_eq!(policy.backoff.initial_delay, Duration::from_millis(5_000));
assert_eq!(policy.backoff.initial_delay, Duration::from_secs(5));
}
#[test]

View file

@ -45,8 +45,7 @@ pub struct RunOptions {
impl RunOptions {
pub fn dry_run_enabled(&self) -> bool {
fabro_config::resolve_run_from_file(&self.settings)
.map(|settings| settings.execution.mode == RunMode::DryRun)
.unwrap_or(false)
.is_ok_and(|settings| settings.execution.mode == RunMode::DryRun)
}
pub fn checkpoint_exclude_globs(&self) -> Vec<String> {

View file

@ -295,7 +295,7 @@ async fn daytona_exec_command_local_timeout() {
// local timeout (duration ~2100ms). Both are valid success conditions for
// the system as a whole avoiding a stall.
assert!(
duration < std::time::Duration::from_millis(3000),
duration < std::time::Duration::from_secs(3),
"Command stalled for longer than the local timeout mechanism"
);
assert!(result.exit_code != 0);
@ -2209,7 +2209,7 @@ async fn daytona_playwright_mcp_sandbox_transport() {
.call_tool(
install_tool,
serde_json::json!({}),
std::time::Duration::from_secs(120),
std::time::Duration::from_mins(2),
)
.await;
match &install_result {

View file

@ -6254,7 +6254,7 @@ mod real_llm {
git: None,
};
let (outcome, state) = tokio::time::timeout(
std::time::Duration::from_secs(120),
std::time::Duration::from_mins(2),
engine.run_with_state(&graph, &run_options),
)
.await
@ -6362,7 +6362,7 @@ mod real_llm {
git: None,
};
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(120),
std::time::Duration::from_mins(2),
engine.run(&graph, &run_options),
)
.await
@ -6494,7 +6494,7 @@ mod real_llm {
git: None,
};
let outcome = tokio::time::timeout(
std::time::Duration::from_secs(120),
std::time::Duration::from_mins(2),
engine.run(&graph, &run_options),
)
.await
@ -7214,14 +7214,14 @@ fn subgraph_node_defaults_scoped_to_subgraph() {
// Plan inherits both thread_id and timeout from subgraph defaults
let plan = &graph.nodes["plan"];
assert_eq!(plan.thread_id(), Some("loop-a"));
assert_eq!(plan.timeout(), Some(std::time::Duration::from_secs(900)));
assert_eq!(plan.timeout(), Some(std::time::Duration::from_mins(15)));
// Implement inherits thread_id but overrides timeout
let implement = &graph.nodes["implement"];
assert_eq!(implement.thread_id(), Some("loop-a"));
assert_eq!(
implement.timeout(),
Some(std::time::Duration::from_secs(1800))
Some(std::time::Duration::from_mins(30))
);
// Outside node should NOT have subgraph defaults
@ -7302,11 +7302,11 @@ fn subgraph_scoping_does_not_leak_to_outer_scope() {
// Inner node gets the subgraph-scoped timeout of 900s
let inner = &graph.nodes["inner_node"];
assert_eq!(inner.timeout(), Some(std::time::Duration::from_secs(900)));
assert_eq!(inner.timeout(), Some(std::time::Duration::from_mins(15)));
// Outer node gets the graph-level default of 300s, not the subgraph's 900s
let outer = &graph.nodes["outer_node"];
assert_eq!(outer.timeout(), Some(std::time::Duration::from_secs(300)));
assert_eq!(outer.timeout(), Some(std::time::Duration::from_mins(5)));
}
#[test]
@ -7331,13 +7331,13 @@ fn subgraph_global_defaults_plus_subgraph_defaults() {
let step = &graph.nodes["step"];
assert_eq!(step.shape(), "box");
assert_eq!(step.thread_id(), Some("loop-thread"));
assert_eq!(step.timeout(), Some(std::time::Duration::from_secs(300)));
assert_eq!(step.timeout(), Some(std::time::Duration::from_mins(5)));
// Plain should have the global defaults but no thread_id
let plain = &graph.nodes["plain"];
assert_eq!(plain.shape(), "box");
assert_eq!(plain.thread_id(), None);
assert_eq!(plain.timeout(), Some(std::time::Duration::from_secs(300)));
assert_eq!(plain.timeout(), Some(std::time::Duration::from_mins(5)));
}
#[test]
@ -7377,7 +7377,7 @@ fn subgraph_without_label_no_class_derived() {
let worker = &graph.nodes["worker"];
assert!(worker.classes.is_empty());
// But the default should still apply
assert_eq!(worker.timeout(), Some(std::time::Duration::from_secs(600)));
assert_eq!(worker.timeout(), Some(std::time::Duration::from_mins(10)));
}
// ---------------------------------------------------------------------------
@ -12239,7 +12239,7 @@ impl Handler for HangingHandler {
_run_dir: &Path,
_services: &fabro_workflow::handler::EngineServices,
) -> Result<Outcome, Error> {
tokio::time::sleep(std::time::Duration::from_secs(60)).await;
tokio::time::sleep(std::time::Duration::from_mins(1)).await;
Ok(Outcome::success())
}
}

View file

@ -269,8 +269,7 @@ async fn debug_page_renders_in_headless_chrome() {
std::process::Command::new("which")
.arg(name)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
.is_ok_and(|o| o.status.success())
});
let Some(chrome_binary) = chrome_binary.copied() else {