jdx/mise · error · eyre::Report

expected closing parenthesis

Error message

expected closing parenthesis

What it means

mise requires that any cask containing pkg artifacts also declares pkgutil receipt ids in its uninstall metadata, mirroring Homebrew's own rule — without pkgutil ids there is no way to track or uninstall an installed pkg. After dedup, if pkgs is non-empty but pkg_ids is empty, the install aborts.

Source

Thrown at crates/aqua-registry/src/template.rs:244

    Ok(left)
}

/// Parse primary expressions
fn parse_primary(tokens: &mut std::iter::Peekable<std::slice::Iter<Token>>) -> Result<Expr> {
    skip_whitespace(tokens);

    let token = tokens.next().wrap_err("unexpected end of expression")?;

    let expr = match token {
        Token::Key(k) => Expr::Var(k.to_string()),
        Token::String(s) => Expr::Literal(s.to_string()),
        Token::LParen => {
            // Parenthesized expression: (func arg)
            skip_whitespace(tokens);
            let inner = parse_pipe(tokens)?;
            skip_whitespace(tokens);
            if !matches!(tokens.next(), Some(Token::RParen)) {
                bail!("expected closing parenthesis");
            }
            inner
        }
        Token::Func(f) => {
            // Function call: func arg1 arg2
            let func_name = f.to_string();
            let mut args = Vec::new();

            // Collect arguments until we hit pipe, rparen, or end
            loop {
                skip_whitespace(tokens);

                match tokens.peek() {
                    None | Some(Token::Pipe) | Some(Token::RParen) => break,
                    Some(Token::Dot) | Some(Token::Ident(_)) => break, // Stop before property access
                    _ => {
                        args.push(parse_arg(tokens)?);
                    }

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. If you maintain the cask, add the pkg's receipt id to the uninstall stanza (get it via pkgutil --pkgs or the pkg's Distribution file)
  2. Install with upstream brew, which may have looser handling, and report the cask to its tap
  3. Update mise in case newer metadata from the API resolves the mismatch

Example fix

# cask JSON
# before
{"artifacts": [{"pkg": ["Installer.pkg"]}], "uninstall": {}}
# after
{"artifacts": [{"pkg": ["Installer.pkg"]}], "uninstall": {"pkgutil": ["com.example.installer"]}}
Defensive patterns

Strategy: validation

Validate before calling

let has_pkg = artifacts.iter().any(|a| artifact_type(a) == "pkg");
let has_pkgutil = cask.uninstall.pkgutil.is_some_and(|ids| !ids.is_empty());
if has_pkg && !has_pkgutil { /* reject the cask before install */ }

Prevention

When it happens

Trigger: Cask JSON with a 'pkg' artifact but no uninstall: {pkgutil: [...]} entry; uninstall metadata listing only delete/quit entries while a pkg stanza exists.

Common situations: Hand-written casks missing the uninstall stanza; third-party taps with incomplete casks; upstream cask lint rules normally catch this, so it usually means non-canonical metadata.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/f6208f74bbb2a7c5. Report an issue: GitHub.