{"record":{"id":"5e571de9ffdf03a1","repo":"zed-industries/zed","slug":"unmatched-in-port-forward-spec-spec","errorCode":null,"errorMessage":"Unmatched '[' in port forward spec: {spec}","messagePattern":"Unmatched '\\[' in port forward spec: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/remote/src/transport/ssh.rs","lineNumber":1596,"sourceCode":"fn parse_port_number(port_str: &str) -> Result<u16> {\n    port_str\n        .parse()\n        .with_context(|| format!(\"parsing port number: {port_str}\"))\n}\n\nfn split_port_forward_tokens(spec: &str) -> Result<Vec<String>> {\n    let mut tokens = Vec::new();\n    let mut chars = spec.chars().peekable();\n\n    while chars.peek().is_some() {\n        if chars.peek() == Some(&'[') {\n            chars.next();\n            let mut bracket_content = String::new();\n            loop {\n                match chars.next() {\n                    Some(']') => break,\n                    Some(ch) => bracket_content.push(ch),\n                    None => anyhow::bail!(\"Unmatched '[' in port forward spec: {spec}\"),\n                }\n            }\n            tokens.push(bracket_content);\n            if chars.peek() == Some(&':') {\n                chars.next();\n            }\n        } else {\n            let mut token = String::new();\n            for ch in chars.by_ref() {\n                if ch == ':' {\n                    break;\n                }\n                token.push(ch);\n            }\n            tokens.push(token);\n        }\n    }\n","sourceCodeStart":1578,"sourceCodeEnd":1614,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/remote/src/transport/ssh.rs#L1578-L1614","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Balance the brackets: put them around the IPv6 literal only — '[::1]:8080:localhost:80'","Re-check shell quoting when supplying the spec so no bracket is consumed","Use plain IPv4 or omit the bind host ('8080:localhost:80') when IPv6 is not required"],"exampleFix":"# before\nssh -L '[8080:localhost:80' user@host\n\n# after\nssh -L '[::1]:8080:localhost:80' user@host","handlingStrategy":"validation","validationCode":"fn port_forward_spec_balanced(spec: &str) -> bool {\n    let mut open = 0usize;\n    for ch in spec.chars() {\n        match ch {\n            '[' => open += 1,\n            ']' => open = open.saturating_sub(1),\n            _ => {}\n        }\n    }\n    open == 0\n}","typeGuard":null,"tryCatchPattern":"match SshConnectionOptions::parse_command_line(input) {\n    Err(e) if e.to_string().contains(\"Unmatched '['\") => {\n        // reject the input early and show the offending spec to the user\n        Err(e)\n    }\n    other => other,\n}","preventionTips":["Validate bracket balance before parsing -L/-R specs","Quote the whole spec when passing through shells to keep brackets intact","Bracket only the IPv6 literal, never the whole spec"],"tags":["ssh","port-forwarding","ipv6","argument-parsing"],"backgroundTag":"port-forward-spec-parse-error","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}