CLI Install Health Check #3573
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CLI Install Health Check | |
| on: | |
| schedule: | |
| - cron: '0 * * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read # gh release list reads the published releases | |
| jobs: | |
| health-check: | |
| name: Test CLI Installation | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| # Resolve the newest PUBLISHED stable @composio/cli release — exactly what install.sh and | |
| # the /releases/latest redirect (humans) resolve and download from. | |
| # | |
| # Drafts are excluded here, so this only ever resolves a fully-published release — an | |
| # in-flight one stays invisible until its assets are verified and attached. | |
| - name: Resolve newest published release | |
| id: resolve | |
| run: | | |
| set -euo pipefail | |
| tag=$(gh release list --repo "${{ github.repository }}" --limit 1000 --exclude-drafts --exclude-pre-releases --json tagName \ | |
| --jq '.[] | select(.tagName | startswith("@composio/cli@")) | .tagName' \ | |
| | sed -n '1p') | |
| if [[ -z "$tag" ]]; then | |
| echo "::error::No published @composio/cli release found" | |
| exit 1 | |
| fi | |
| echo "tag=${tag}" >> "$GITHUB_OUTPUT" | |
| echo "Latest published release: ${tag}" | |
| # Install the PINNED newest tag. This is the path that 404s when a release ships without | |
| # assets. The no-arg `curl | sh` below is asset-aware and silently falls back to the | |
| # previous good release, so it would stay green even when the newest release is broken — | |
| # which is exactly why the previous canary never caught the recurring outage. | |
| - name: Install newest stable (pinned — fails on a release with missing assets) | |
| run: | | |
| set -euo pipefail | |
| # Install-only leg: this step's signal is asset availability, so it opts out of | |
| # automatic shell setup with COMPOSIO_INSTALL_SHELL=none. Default-flow (auto) | |
| # coverage stays with the no-arg leg below. | |
| curl -fsSL https://composio.dev/install | COMPOSIO_INSTALL_SHELL=none sh -s -- "${{ steps.resolve.outputs.tag }}" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Verify pinned installation | |
| run: | | |
| set -euo pipefail | |
| ls -la "$HOME/.composio" || true | |
| test -x "$HOME/.composio/composio" | |
| test -L "$HOME/.local/bin/composio" | |
| which composio | |
| composio --version | |
| # Secondary: the default no-arg flow most users run, exactly as they run it — including | |
| # automatic shell setup (the default infers the shell from $SHELL; the idempotent rc | |
| # edit on this ephemeral runner is harmless). Asset-aware and self-healing, so it is a | |
| # coverage net rather than the primary signal. | |
| # | |
| # This leg is also the canary for the auto shell-setup default itself: shell-setup | |
| # failure is non-fatal in install.sh (exit 0 + warning), so without asserting the | |
| # actual contract below, a broken default would install a working binary and still | |
| # report green. SHELL=/bin/bash pins deterministic auto-resolution on this runner, and | |
| # the assertions mirror cli.test-installation.yml's checks — the managed PATH block in | |
| # ~/.bashrc, plus that a login shell can actually resolve the binary. | |
| # | |
| # COMPOSIO_BIN_DIR is pinned to a NON-default directory ($HOME/.composio-canary/bin) | |
| # to isolate managed-PATH reachability: Ubuntu's skel ~/.profile already adds | |
| # ~/.local/bin to PATH, and the ~/.bash_profile the installer seeds keeps sourcing | |
| # ~/.profile — so with the default bin dir the login-shell probe below would find | |
| # composio even if the managed block did nothing. No startup file but the managed | |
| # block ever adds the canary dir, so resolving composio from it proves the block. | |
| # | |
| # Deployment coupling: like the production legs in cli.install-e2e.yml, this installs | |
| # from the PRODUCTION installer (composio.dev/install), so the shell-setup assertions | |
| # only pass once the auto-default ships there. This workflow is schedule/workflow_dispatch | |
| # only (no pull_request trigger), so it never runs against a pre-deploy PR or branch. | |
| - name: Install via default no-arg flow | |
| run: | | |
| set -euo pipefail | |
| rm -f \ | |
| "$HOME/.local/bin/composio" \ | |
| "$HOME/.composio/composio" \ | |
| "$HOME/.composio/release-tag.txt" \ | |
| "$HOME/.composio/run-helpers-runtime.mjs" \ | |
| "$HOME/.composio/run-subagent-shared.mjs" \ | |
| "$HOME/.composio/run-subagent-acp.mjs" \ | |
| "$HOME/.composio/run-subagent-legacy.mjs" \ | |
| "$HOME/.composio/run-subagent-output-mcp.mjs" | |
| rm -rf \ | |
| "$HOME/.composio/services" \ | |
| "$HOME/.composio/acp-adapters" \ | |
| "$HOME/.composio/local-tools-binaries" | |
| # Torn down separately, and deliberately not part of the block above: | |
| # test/release-workflow.test.ts pins that block against INSTALL.md's | |
| # documented uninstall, and the canary bin dir is this workflow's own | |
| # isolation directory rather than something a user would ever remove. | |
| rm -rf "$HOME/.composio-canary" | |
| curl -fsSL https://composio.dev/install | SHELL=/bin/bash COMPOSIO_BIN_DIR="$HOME/.composio-canary/bin" sh | |
| test -x "$HOME/.composio/composio" | |
| test -L "$HOME/.composio-canary/bin/composio" | |
| "$HOME/.composio-canary/bin/composio" --version | |
| # The pinned leg's default entry point was removed above and must stay | |
| # absent: the canary bin dir is the only place this install may surface | |
| # the binary, so nothing outside the managed block can resolve it. | |
| test ! -e "$HOME/.local/bin/composio" | |
| # Assert the auto shell-setup contract: the managed PATH block landed in ~/.bashrc. | |
| if grep -Fqx '# Composio CLI' "$HOME/.bashrc" \ | |
| && grep -Fqx 'export PATH="$HOME/.composio-canary/bin:$PATH"' "$HOME/.bashrc"; then | |
| echo "✅ ~/.bashrc gained the managed Composio PATH block via automatic shell setup" | |
| else | |
| echo "❌ ~/.bashrc missing the managed Composio PATH block after the no-argument install" | |
| echo "=== ~/.bashrc contents ===" | |
| cat "$HOME/.bashrc" || true | |
| exit 1 | |
| fi | |
| # Assert the login-mode startup file (the file a login bash actually reads — | |
| # ~/.bashrc is not it) also carries the managed block. Mirrors install.sh's | |
| # bash_login_path_file selection: first existing of ~/.bash_profile or | |
| # ~/.bash_login, else the ~/.bash_profile the installer creates. | |
| if [ ! -f "$HOME/.bash_profile" ] && [ -f "$HOME/.bash_login" ]; then | |
| login_file="$HOME/.bash_login" | |
| else | |
| login_file="$HOME/.bash_profile" | |
| fi | |
| if grep -Fqx '# Composio CLI' "$login_file" \ | |
| && grep -Fqx 'export PATH="$HOME/.composio-canary/bin:$PATH"' "$login_file"; then | |
| echo "✅ $login_file gained the managed Composio PATH block via automatic shell setup" | |
| else | |
| echo "❌ $login_file missing the managed Composio PATH block after the no-argument install" | |
| echo "=== $login_file contents ===" | |
| cat "$login_file" || true | |
| exit 1 | |
| fi | |
| # Assert a login shell actually resolves the binary through the MANAGED | |
| # PATH block. env -i drops the step's inherited PATH (GITHUB_PATH from the | |
| # pinned-tag step), and the non-default canary bin dir defeats the other | |
| # leak: ~/.profile — which the installer-seeded ~/.bash_profile keeps | |
| # sourcing — prepends ~/.local/bin, never $HOME/.composio-canary/bin. The | |
| # probe therefore requires the resolved path to be the canary entry point. | |
| # This mirrors the harness's assert_login_bash_path clean-environment probe. | |
| resolved=$(env -i HOME="$HOME" PATH=/usr/bin:/bin bash -ilc 'command -v composio' || true) | |
| if [ "$resolved" = "$HOME/.composio-canary/bin/composio" ] \ | |
| && env -i HOME="$HOME" PATH=/usr/bin:/bin bash -ilc 'composio --version'; then | |
| echo "✅ composio resolves to the managed canary bin dir via a login bash shell" | |
| else | |
| echo "❌ composio did not resolve to \$HOME/.composio-canary/bin/composio via a login bash shell (got: ${resolved:-nothing})" | |
| exit 1 | |
| fi | |
| - name: Send Slack Notification (Failure) | |
| if: failure() | |
| uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 | |
| with: | |
| webhook: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }} | |
| webhook-type: incoming-webhook | |
| payload: | | |
| { | |
| "text": "⚠️ CLI Install Health Check Failed!\n*Repository:* ${{ github.repository }}\n*Workflow:* ${{ github.workflow }}\n*Run:* ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Commit:* ${{ github.sha }}" | |
| } |