facebook/flow · error · std::io::Error

Unterminated quote in argument string: {arg_string}

Error message

Unterminated quote in argument string: {arg_string}

What it means

check_exec_file_promise parses an exec-file test fixture's argument string with shell-like rules: single/double quotes group words and a backslash escapes the next character. The parser tracks the open quote across the whole string; if a quote is opened and never closed before end-of-input, it aborts with ErrorKind::InvalidInput "Unterminated quote in argument string: {arg_string}" (the full offending string is included).

Source

Thrown at rust_port/crates/flow_dev_tools/src/runtests/check_exec_file_promise.rs:111

            had_quote = true;
        } else if ch.is_whitespace() {
            if !current.is_empty() || had_quote {
                args.push(std::mem::take(&mut current));
                had_quote = false;
            }
        } else if ch == '\\' {
            if let Some(next) = chars.next() {
                current.push(next);
            } else {
                current.push('\\');
            }
        } else {
            current.push(ch);
        }
    }

    if quote.is_some() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            format!("Unterminated quote in argument string: {arg_string}"),
        ));
    }
    if !current.is_empty() || had_quote {
        args.push(current);
    }
    Ok(args)
}

pub(super) fn exec_file(
    cmd: &str,
    args: &[String],
    options: &ExecOptions,
    stdin_data: Option<&str>,
) -> io::Result<ExecResult> {
    flow_tokio_runtime::block_on(async {
        let mut command = Command::new(cmd);

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Balance the quotes in the fixture's argument string — every opening quote needs a closing one.
  2. Escape literal quotes with a backslash (\") or wrap the word in the other quote type ("it's" instead of it's).
  3. After any JSON/tooling round-trip of the fixture, re-check that escapes survived (a consumed closing quote is the usual damage).

Example fix

# before (exec file arg string): opening double quote never closed
run flow check --focus " Unclosed

# after: close the quote, or drop it entirely
run flow check --focus "Unclosed"
run flow check --focus Unclosed
Defensive patterns

Strategy: validation

Validate before calling

// Reject exec-file arg strings with unbalanced quotes before running the check.
fn quotes_balanced(s: &str) -> bool {
    let (mut in_sq, mut in_dq, mut esc) = (false, false, false);
    for c in s.chars() {
        if esc { esc = false; continue; }
        match c {
            '\\' => esc = true,
            '\'' if !in_dq => in_sq = !in_sq,
            '"' if !in_sq => in_dq = !in_dq,
            _ => {}
        }
    }
    !in_sq && !in_dq
}

Type guard

fn is_unterminated_quote(e: &std::io::Error) -> bool {
    e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("Unterminated quote")
}

Try / catch

On InvalidInput 'Unterminated quote', report the exec-file name and its args field (the message already embeds the full string) — guide the fix to the fixture, not to the command being run.

Prevention

When it happens

Trigger: An exec-file fixture whose args string contains an unmatched quote character, e.g. `--focus "Unclosed` or `it's`, where the quote opens a group that never closes before the end of the string.

Common situations: Hand-written exec fixtures; apostrophes inside unquoted words (don't, it's) treated as quote starts; a closing quote lost through JSON double-escaping when the fixture was machine-generated; copy-pasting shell lines into the fixture without rebalancing quotes.

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/ce8784dd94c21bc1. Report an issue: GitHub.