Migrate duo-cli installation in CI workloads from npm to binary

Problem

The Duo CLI (@gitlab/duo-cli) is currently installed inside CI workloads via npm, as seen in ee/app/services/ai/duo_workflows/start_workflow_service.rb:

DUO_CLI_VERSION = "8.92.0"

# In set_up_executor_commands:
cli_install_command = [
  "command -v duo > /dev/null 2>&1 && ",
  "echo \"duo-cli already present, skipping installation\" || ",
  "{ echo \"Installing @gitlab/duo-cli@#{DUO_CLI_VERSION}...\" && ",
  "npm install -g @gitlab/duo-cli@#{DUO_CLI_VERSION}; }"
].join

This approach has several significant drawbacks:

  • Node.js/npm version conflicts — the CI runner image must have a compatible Node.js and npm version. Mismatches cause hard-to-debug installation failures that are outside our control.
  • npm registry dependency — a CI job failure can be caused by npm registry availability or package publishing issues, as evidenced by the existing CI check in .gitlab/ci/setup.gitlab-ci.yml that verifies npm availability before every pipeline.
  • PATH fragility — after npm install -g, the binary must be explicitly added to $PATH via export PATH="$(npm bin -g):$PATH", adding brittle shell scripting to every workload.
  • Blocks OpenTUI migration — the planned migration of Duo CLI's TUI from ink to OpenTUI requires the Bun runtime, which is incompatible with npm distribution. This blocks improvements tracked in the parent epic gitlab-org&22002.
  • Doubles release surface — every Duo CLI release must be published and validated through both npm and binary channels, increasing maintenance overhead.

Desired Outcome

The set_up_executor_commands method in StartWorkflowService installs the duo binary by downloading a precompiled binary directly (e.g., from the GitLab package registry or a CDN), rather than using npm install -g. This mirrors how glab duo cli already works — auto-downloading the binary on first run.

Specifically:

  • The cli_install_command in StartWorkflowService#set_up_executor_commands is replaced with a binary download script (e.g., using curl or wget) that fetches the correct versioned binary for the runner's platform/architecture.
  • The DUO_CLI_VERSION constant continues to pin the version, but now resolves to a binary artifact URL rather than an npm package version.
  • The CI check in .gitlab/ci/setup.gitlab-ci.yml (verify-start-workflow-service-assets) is updated to validate binary artifact availability instead of npm registry availability.
  • No Node.js or npm is required in the runner environment for CLI installation.

Implementation Plan

  1. Update StartWorkflowService#set_up_executor_commands in ee/app/services/ai/duo_workflows/start_workflow_service.rb

  2. Update the CI asset verification job in .gitlab/ci/setup.gitlab-ci.yml

    • Replace the npm view @gitlab/duo-cli@${DUO_CLI_VERSION} version check with a check that the binary artifact URL for the pinned version is reachable (e.g., curl --head the download URL)
  3. Update specs in ee/spec/services/ai/duo_workflows/start_workflow_service_spec.rb

    • Update cli_install_command and cli_install_commands let blocks to reflect the new binary download command
    • Ensure platform detection logic (if any) is covered
  4. Smoke test the updated workload in a real CI environment to confirm the binary is downloaded, made executable, and duo run executes successfully without Node.js present

Edited by Sebastian Rehm