zed-industries/zed · error

Unmatched '[' in port forward spec: {spec}

Error message

Unmatched '[' in port forward spec: {spec}

What it means

Port-forward specs parsed from ssh command-line options (-L/-R style, e.g. [::1]:8080:localhost:80) are split by split_port_forward_tokens; IPv6 literals are handled as bracket-delimited groups, and an opening '[' with no matching ']' before end-of-string bails, echoing the offending spec.

Source

Thrown at crates/remote/src/transport/ssh.rs:1596

fn parse_port_number(port_str: &str) -> Result<u16> {
    port_str
        .parse()
        .with_context(|| format!("parsing port number: {port_str}"))
}

fn split_port_forward_tokens(spec: &str) -> Result<Vec<String>> {
    let mut tokens = Vec::new();
    let mut chars = spec.chars().peekable();

    while chars.peek().is_some() {
        if chars.peek() == Some(&'[') {
            chars.next();
            let mut bracket_content = String::new();
            loop {
                match chars.next() {
                    Some(']') => break,
                    Some(ch) => bracket_content.push(ch),
                    None => anyhow::bail!("Unmatched '[' in port forward spec: {spec}"),
                }
            }
            tokens.push(bracket_content);
            if chars.peek() == Some(&':') {
                chars.next();
            }
        } else {
            let mut token = String::new();
            for ch in chars.by_ref() {
                if ch == ':' {
                    break;
                }
                token.push(ch);
            }
            tokens.push(token);
        }
    }

View on GitHub (pinned to f4178619ac)

Solutions

  1. Balance the brackets: put them around the IPv6 literal only — '[::1]:8080:localhost:80'
  2. Re-check shell quoting when supplying the spec so no bracket is consumed
  3. Use plain IPv4 or omit the bind host ('8080:localhost:80') when IPv6 is not required

Example fix

# before
ssh -L '[8080:localhost:80' user@host

# after
ssh -L '[::1]:8080:localhost:80' user@host
Defensive patterns

Strategy: validation

Validate before calling

fn port_forward_spec_balanced(spec: &str) -> bool {
    let mut open = 0usize;
    for ch in spec.chars() {
        match ch {
            '[' => open += 1,
            ']' => open = open.saturating_sub(1),
            _ => {}
        }
    }
    open == 0
}

Try / catch

match SshConnectionOptions::parse_command_line(input) {
    Err(e) if e.to_string().contains("Unmatched '['") => {
        // reject the input early and show the offending spec to the user
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: parse_port_forward_spec routes a spec containing '[' into split_port_forward_tokens, whose inner loop hits None before finding ']' — e.g. '[::1:8080:localhost:80' or any truncated/misquoted IPv6 bind address.

Common situations: Hand-writing port forward options and dropping the closing bracket around an IPv6 literal; shell quoting that strips ']' when passing the argument; copy-pasting from docs or chat that mangled bracket characters.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/5e571de9ffdf03a1. Report an issue: GitHub.