From aea7dcb5ecc0dfa1f2325194ee81fcef0dab20c3 Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Sat, 17 Oct 2026 17:02:43 +0000 Subject: [PATCH] fix(cli): emit JSON for recording validation errors --- apps/cli/src/record.rs | 36 ++++++++++++------------------------ apps/cli/tests/cli.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/apps/cli/src/record.rs b/apps/cli/src/record.rs index 7ffa7b45966..b628a154a9f 100644 --- a/apps/cli/src/record.rs +++ b/apps/cli/src/record.rs @@ -173,6 +173,18 @@ pub struct RecordStopArgs { impl RecordStart { pub async fn run(self, json: bool) -> Result<(), String> { let format = resolve_format(json, self.format); + match self.run_inner(format).await { + Ok(()) => Ok(()), + Err(error) => { + if format == OutputFormat::Json { + let _ = write_json_line(&RecordEvent::Error { error: &error }); + } + Err(error) + } + } + } + + async fn run_inner(self, format: OutputFormat) -> Result<(), String> { self.params.validate()?; if self.detach { run_detached(self.params, format).await @@ -183,18 +195,6 @@ impl RecordStart { } async fn run_foreground(params: RecordParams, format: OutputFormat) -> Result<(), String> { - match foreground_inner(params, format).await { - Ok(()) => Ok(()), - Err(error) => { - if format == OutputFormat::Json { - let _ = write_json_line(&RecordEvent::Error { error: &error }); - } - Err(error) - } - } -} - -async fn foreground_inner(params: RecordParams, format: OutputFormat) -> Result<(), String> { let interactive = std::io::stdin().is_terminal(); // Without --duration the recorder blocks for a stop signal. A non-interactive caller (agent, CI, @@ -250,18 +250,6 @@ fn automation_mode(mode: RecordMode) -> cap_automation::AutomationRecordingMode } async fn run_detached(params: RecordParams, format: OutputFormat) -> Result<(), String> { - match detached_inner(params, format).await { - Ok(()) => Ok(()), - Err(error) => { - if format == OutputFormat::Json { - let _ = write_json_line(&RecordEvent::Error { error: &error }); - } - Err(error) - } - } -} - -async fn detached_inner(params: RecordParams, format: OutputFormat) -> Result<(), String> { let recording_id = new_recording_id(); // Validate the target up front so a bad --screen/--window fails fast with a clear message rather // than surfacing later from the background worker's log. diff --git a/apps/cli/tests/cli.rs b/apps/cli/tests/cli.rs index 14d3b52fbed..69243786bdc 100644 --- a/apps/cli/tests/cli.rs +++ b/apps/cli/tests/cli.rs @@ -372,6 +372,36 @@ fn record_rejects_zero_fps() { assert!(!output.status.success()); } +#[test] +fn record_validation_failures_emit_json_error_events() { + for command in [ + &["record"][..], + &["record", "start"][..], + &["record", "start", "--detach"][..], + ] { + for json_flag in [&["--json"][..], &["--format", "json"][..]] { + for (params, expected_error) in [ + (&["--duration", "0"][..], "Duration"), + (&["--duration", "NaN"][..], "Duration"), + (&["--duration", "inf"][..], "Duration"), + (&["--fps", "0", "--duration", "1"][..], "--fps"), + (&["--duration", "1"][..], "No target specified"), + ] { + let mut args = command.to_vec(); + args.extend_from_slice(json_flag); + args.extend_from_slice(params); + let output = run(&args); + assert_eq!(output.status.code(), Some(1), "args: {args:?}"); + let json = parse_json(&output); + assert_eq!(json["type"], "error", "args: {args:?}"); + let error = json["error"].as_str().expect("expected an error message"); + assert!(error.contains(expected_error), "args: {args:?}: {error}"); + assert!(stderr(&output).contains(error), "args: {args:?}"); + } + } + } +} + #[test] fn export_rejects_settings_json_with_flags() { let output = run(&[