@@ -473,6 +473,42 @@ async fn run_local_infrastructure_with_timeout(
473473 }
474474}
475475
476+ const DEV_DOCKERLESS_HINT : & str =
477+ "Docker is unavailable. For local development without Docker, rerun: moose dev --dockerless" ;
478+
479+ fn format_local_infrastructure_error (
480+ error : & anyhow:: Error ,
481+ include_dockerless_hint : bool ,
482+ ) -> String {
483+ let diagnostics = format ! ( "{error:?}" ) ;
484+ let existing_message = format ! ( "Failed to run local infrastructure: {diagnostics}" ) ;
485+
486+ if include_dockerless_hint && is_container_runtime_unavailable_error ( & diagnostics) {
487+ format ! ( "{DEV_DOCKERLESS_HINT}\n \n {existing_message}" )
488+ } else {
489+ existing_message
490+ }
491+ }
492+
493+ fn is_container_runtime_unavailable_error ( diagnostics : & str ) -> bool {
494+ let diagnostics = diagnostics. to_ascii_lowercase ( ) ;
495+ [
496+ "failed to run docker commands" ,
497+ "to ensure docker is running" ,
498+ "is docker running" ,
499+ "cannot connect to the docker daemon" ,
500+ "docker daemon" ,
501+ "no such file or directory: docker" ,
502+ "no such file or directory: finch" ,
503+ "os error 2: docker" ,
504+ "os error 2: finch" ,
505+ "finch is not running" ,
506+ "cannot connect to the finch vm" ,
507+ ]
508+ . iter ( )
509+ . any ( |phrase| diagnostics. contains ( phrase) )
510+ }
511+
476512pub async fn top_command_handler (
477513 settings : Settings ,
478514 commands : & Commands ,
@@ -744,7 +780,7 @@ pub async fn top_command_handler(
744780 . map_err ( |e| {
745781 RoutineFailure :: error ( Message {
746782 action : "Dev" . to_string ( ) ,
747- details : format ! ( "Failed to run local infrastructure: {e:?}" ) ,
783+ details : format_local_infrastructure_error ( & e , ! * dockerless ) ,
748784 } )
749785 } ) ?;
750786 } else {
@@ -2389,6 +2425,81 @@ mod tests {
23892425
23902426 use super :: * ;
23912427
2428+ #[ test]
2429+ fn dev_infrastructure_error_suggests_dockerless_when_docker_binary_is_missing ( ) {
2430+ let error = anyhow:: anyhow!( "Failed: No such file or directory: docker (os error 2)" ) ;
2431+
2432+ let details = format_local_infrastructure_error ( & error, true ) ;
2433+
2434+ assert ! ( details. contains( "moose dev --dockerless" ) ) ;
2435+ assert ! ( details. contains( "Failed: No such file or directory: docker (os error 2)" ) ) ;
2436+ }
2437+
2438+ #[ test]
2439+ fn dev_infrastructure_error_suggests_dockerless_when_wrapped_docker_check_fails ( ) {
2440+ let error = anyhow:: anyhow!(
2441+ "Failed: to ensure docker is running\n Caused by:\n No such file or directory (os error 2)"
2442+ ) ;
2443+
2444+ let details = format_local_infrastructure_error ( & error, true ) ;
2445+
2446+ assert ! ( details. contains( "moose dev --dockerless" ) ) ;
2447+ assert ! ( details. contains( "to ensure docker is running" ) ) ;
2448+ }
2449+
2450+ #[ test]
2451+ fn dev_infrastructure_error_suggests_dockerless_when_docker_daemon_is_unreachable ( ) {
2452+ let error = anyhow:: anyhow!(
2453+ "Failed: Cannot connect to the Docker daemon at unix:///var/run/docker.sock"
2454+ ) ;
2455+
2456+ let details = format_local_infrastructure_error ( & error, true ) ;
2457+
2458+ assert ! ( details. contains( "moose dev --dockerless" ) ) ;
2459+ assert ! ( details. contains( "Cannot connect to the Docker daemon" ) ) ;
2460+ }
2461+
2462+ #[ test]
2463+ fn local_infrastructure_error_does_not_suggest_dockerless_for_non_dev_paths ( ) {
2464+ let error = anyhow:: anyhow!( "Failed: Cannot connect to the Docker daemon" ) ;
2465+
2466+ let details = format_local_infrastructure_error ( & error, false ) ;
2467+
2468+ assert ! ( !details. contains( "moose dev --dockerless" ) ) ;
2469+ assert_eq ! (
2470+ details,
2471+ "Failed to run local infrastructure: Failed: Cannot connect to the Docker daemon"
2472+ ) ;
2473+ }
2474+
2475+ #[ test]
2476+ fn dev_infrastructure_error_does_not_suggest_dockerless_for_non_runtime_errors ( ) {
2477+ let error = anyhow:: anyhow!( "Failed: ClickHouse container did not become healthy" ) ;
2478+
2479+ let details = format_local_infrastructure_error ( & error, true ) ;
2480+
2481+ assert ! ( !details. contains( "moose dev --dockerless" ) ) ;
2482+ assert_eq ! (
2483+ details,
2484+ "Failed to run local infrastructure: Failed: ClickHouse container did not become healthy"
2485+ ) ;
2486+ }
2487+
2488+ #[ test]
2489+ fn dev_infrastructure_error_does_not_suggest_dockerless_for_unrelated_missing_files ( ) {
2490+ let error = anyhow:: anyhow!(
2491+ "Failed: unable to read config.yaml: No such file or directory (os error 2)"
2492+ ) ;
2493+
2494+ let details = format_local_infrastructure_error ( & error, true ) ;
2495+
2496+ assert ! ( !details. contains( "moose dev --dockerless" ) ) ;
2497+ assert_eq ! (
2498+ details,
2499+ "Failed to run local infrastructure: Failed: unable to read config.yaml: No such file or directory (os error 2)"
2500+ ) ;
2501+ }
2502+
23922503 fn set_test_temp_dir ( ) {
23932504 let test_dir = "tests/tmp" ;
23942505 // check that the directory isn't already set to test_dir
0 commit comments