windmill-labs/windmill · error

Impossible to parse arg digit

Error message

Impossible to parse arg digit

What it means

While building the Bash signature, the parser maps each captured positional index (`$1`, `${2}`, `${3:-default}`) to an i32 via capture groups 2/3/4 of RE_BASH. If none of those groups is present or the digits can't parse, it errors with this message — an inconsistency between the regex alternations and the capture-conversion code, normally not user-triggerable.

Source

Thrown at backend/parsers/windmill-parser-bash/src/lib.rs:119

        })
    } else {
        Err(anyhow!("Error parsing powershell script".to_string()))
    }
}

lazy_static::lazy_static! {
    static ref RE_BASH: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+)\}|\{(\d+):-(.*)\})"(?:[\t ]*)?(?:#.*)?\r?$"#).unwrap();
}

fn parse_bash_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
    let mut hm: HashMap<i32, (String, Option<String>)> = HashMap::new();
    for cap in RE_BASH.captures_iter(code) {
        hm.insert(
            cap.get(2)
                .or(cap.get(3))
                .or(cap.get(4))
                .and_then(|x| x.as_str().parse::<i32>().ok())
                .ok_or_else(|| anyhow!("Impossible to parse arg digit"))?,
            (
                cap[1].to_string(),
                cap.get(5).map(|x| x.as_str().to_string()),
            ),
        );
    }
    let mut args = vec![];
    for i in 1..20 {
        if hm.contains_key(&i) {
            let (name, default) = hm.get(&i).unwrap();
            args.push(Arg {
                name: name.clone(),
                typ: Typ::Str(None),
                default: default.clone().map(|x| json!(x)),
                otyp: None,
                has_default: default.is_some(),
                oidx: None,
                otyp_inferred: false,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Rewrite the offending `$N`-style line in the script to the canonical `name="$1"` form
  2. If RE_BASH was patched, extend the capture chain (`.or(cap.get(N))`) and the i32 parse to cover the new alternation
  3. Report upstream if a plain `var="$1"` line triggers it — likely a parser bug

Example fix

// before
.and_then(|x| x.as_str().parse::<i32>().ok())
// after (when a new capture group 6 was added to RE_BASH)
.or(cap.get(6))
.and_then(|x| x.as_str().parse::<i32>().ok())
Defensive patterns

Strategy: type-guard

Validate before calling

// only submit scripts with canonical arg assignments
const canon = /^\w+="\$(?:\d+|\{\d+\}|\{\d+:-.*\})"/m;
if (!canon.test(code)) throw new Error('no canonical bash positional args');

Type guard

fn has_canonical_bash_args(code: &str) -> bool {
    lazy_static::lazy_static! {
        static ref RE: regex::Regex = regex::Regex::new(
            r#"^\w+="\$(?:\d+|\{\d+\}|\{\d+:-.*\})""#).unwrap();
    }
    RE.is_match(code)
}

Prevention

When it happens

Trigger: A script line matched RE_BASH where groups 2/3/4 are all None or non-numeric — practically triggered by regex/code drift (an alternation added to the regex whose digit capture isn't in the `.or(...)` chain) or exotic input matching an uncovered alternation.

Common situations: Custom forks or patches to RE_BASH adding new alternations without extending the `.get(2).or(get(3)).or(get(4))` chain; scripts with oddly formatted arg lines hitting an edge alternation.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/3f37df0ba2f5dc6e. Report an issue: GitHub.