Hmbown/CodeWhale · error · TypeError

pipeline(): expected an array of items

Error message

pipeline(): expected an array of items

What it means

plugin_target_path computes the destination directory name from the plugin's name and runs it through validate_skill_name_segment (skills/install.rs:1536). A name is rejected if it is empty, has leading/trailing or any internal whitespace, equals '.' or '..', contains '/' or '\\', or is not a single normal path component. This is a path-traversal guard: the name becomes a directory under the user plugins dir, so it must be exactly one safe segment.

Source

Thrown at crates/workflow-js/src/vm.rs:1095

    }
    return Promise.all(thunks.map((thunk) => {
      try {
        return Promise.resolve(typeof thunk === "function" ? thunk() : thunk).catch((err) => {
          if (isFatalTaskError(err)) throw err;
          hostLog("parallel(): dropped a failed slot as null: " + String((err && err.message) || err));
          return null;
        });
      } catch (err) {
        if (isFatalTaskError(err)) return Promise.reject(err);
        hostLog("parallel(): dropped a failed slot as null: " + String((err && err.message) || err));
        return null;
      }
    }));
  };

  globalThis.pipeline = (items, ...stages) => {
    if (!Array.isArray(items)) {
      throw new TypeError("pipeline(): expected an array of items");
    }
    if (items.length > MAX_ITEMS) {
      throw new Error("pipeline(): max " + MAX_ITEMS + " items per call");
    }
    return Promise.all(items.map(async (item, index) => {
      let value = item;
      for (const stage of stages) {
        try {
          value = await stage(value, item, index);
        } catch (err) {
          if (isFatalTaskError(err)) throw err;
          hostLog("pipeline(): dropped item " + index + " as null: " + String((err && err.message) || err));
          return null;
        }
      }
      return value;
    }));
  };

View on GitHub (pinned to 8880682c63)

Solutions

  1. Change the manifest name to a slug: lowercase letters, digits, and hyphens only, no spaces or path separators (e.g. "my-plugin").
  2. Check for invisible characters: leading/trailing spaces, non-breaking spaces, or a BOM before the name in the JSON.
  3. Keep a separate display/description field for the pretty name; the name field is a directory identifier.

Example fix

// before (plugin.json)
{ "plugin": { "name": "My Fancy Plugin", ... } }

// after
{ "plugin": { "name": "my-fancy-plugin", "description": "My Fancy Plugin" } }
Defensive patterns

Strategy: validation

Validate before calling

let name = &manifest.plugin.name;
let safe = !name.is_empty()
    && name.trim() == name
    && !name.chars().any(char::is_whitespace)
    && !name.contains('/') && !name.contains('\\')
    && name != "." && name != "..";
if !safe {
    anyhow::bail!("choose a slug-style plugin name (letters, digits, hyphens)");
}

Type guard

fn is_safe_plugin_name(name: &str) -> bool {
    !name.is_empty()
        && name.trim() == name
        && !name.chars().any(char::is_whitespace)
        && !matches!(name, "." | "..")
        && !name.contains('/') && !name.contains('\\')
}

Try / catch

match plugin_target_path(&name, &plugins_dir) {
    Ok(path) => path,
    Err(e) if e.to_string().contains("not a safe directory name") => {
        let slug: String = name.trim().to_lowercase()
            .chars().map(|c| if c.is_ascii_alphanumeric() { c } else { '-' }).collect();
        plugin_target_path(slug.trim_matches('-'), &plugins_dir)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Installing a plugin whose manifest [plugin].name is e.g. "my plugin" (space), "../escape", "a/b", "" or " padded "; plugin_target_path then refuses to build the target path.

Common situations: Hand-authored plugin.json with a human-readable display name in the name field; names copied from a docs headline with trailing whitespace; malicious or malformed third-party bundles attempting traversal; version differences where an older installer accepted names the current one rejects.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/f32a41dcab870cc9. Report an issue: GitHub.