diff --git a/.github/workflows/check-tools.yml b/.github/workflows/check-tools.yml index bd4bc2a0..4755b577 100644 --- a/.github/workflows/check-tools.yml +++ b/.github/workflows/check-tools.yml @@ -56,3 +56,6 @@ jobs: - name: Reject unclosed agent frontmatter run: bash scripts/test-frontmatter-closing.sh + + - name: Keep installing after one tool fails, then exit non-zero + run: bash scripts/test-install-continue-after-failure.sh diff --git a/scripts/install.sh b/scripts/install.sh index f8ccb7fa..c0828abb 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1676,7 +1676,8 @@ main() { fi printf "\n" - local installed=0 t i=0 + local installed=0 t i=0 rc + local failed=() if $use_parallel; then local install_out_dir install_out_dir="$(mktemp -d)" @@ -1695,16 +1696,37 @@ main() { progress_bar "$i" "$n_selected" printf "\n" printf " ${C_DIM}[%s/%s]${C_RESET} %s\n" "$i" "$n_selected" "$t" - install_tool "$t" - (( installed++ )) || true + # One tool failing must not cost the tools after it. A bare + # install_tool under set -e exited the whole script at the first + # `return 1`, so a missing integrations/cursor meant qwen, codex and + # every later tool were never tried and nothing said so. + # + # Not `install_tool "$t" || ...`: bash ignores errexit inside anything + # run on the left of || (subshell included), so a failing cp inside a + # tool would carry on as if it had worked. The subshell turns errexit + # back on for itself while the parent's is off for this one command. + set +e + ( set -e; install_tool "$t" ) + rc=$? + set -e + if (( rc == 0 )); then + (( installed++ )) || true + else + failed+=("$t") + fi done fi # Done box local msg=" Done! Installed $installed tool(s)." + (( ${#failed[@]} )) && msg=" Installed $installed of $n_selected tool(s)." printf "\n" box_top - box_row "${C_GREEN}${C_BOLD}${msg}${C_RESET}" + if (( ${#failed[@]} )); then + box_row "${C_YELLOW}${C_BOLD}${msg}${C_RESET}" + else + box_row "${C_GREEN}${C_BOLD}${msg}${C_RESET}" + fi box_bot printf "\n" if [[ -s "$SKIPPED_LOG" ]]; then @@ -1714,6 +1736,10 @@ main() { fi dim " Run ./scripts/convert.sh to regenerate after adding or editing agents." printf "\n" + if (( ${#failed[@]} )); then + err "Failed: ${failed[*]} — see the [ERR] line under each above. The other tools installed." + exit 1 + fi } main "$@" diff --git a/scripts/test-install-continue-after-failure.sh b/scripts/test-install-continue-after-failure.sh new file mode 100755 index 00000000..be454198 --- /dev/null +++ b/scripts/test-install-continue-after-failure.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# One tool failing in a sequential install must not cost the tools after it, +# and the install must still exit non-zero and name the tool that failed. +# +# Before: install_tool ran bare under set -e, so the first tool whose output +# was missing exited the script. With gemini-cli, cursor and qwen selected and +# no integrations/cursor, gemini-cli installed, cursor printed its [ERR], and +# qwen was never attempted and never mentioned. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +tmp="$(mktemp -d "${TMPDIR:-/tmp}/agency-continue.XXXXXX")" +trap 'rm -rf "$tmp"' EXIT + +fail() { echo "FAIL: $*" >&2; sed 's/^/ /' "$tmp/output" >&2; exit 1; } + +mkdir -p "$tmp/repo/scripts" "$tmp/repo/engineering" \ + "$tmp/repo/integrations/gemini-cli/agents" "$tmp/repo/integrations/qwen/agents" \ + "$tmp/home/project" +cp "$SCRIPT_DIR/install.sh" "$SCRIPT_DIR/lib.sh" "$tmp/repo/scripts/" +cat > "$tmp/repo/divisions.json" <<'EOF' +{ + "divisions": { + "engineering": {} + } +} +EOF +cat > "$tmp/repo/engineering/agent.md" <<'EOF' +--- +name: Sample Agent +description: Example agent +--- +Instructions. +EOF +printf 'Gemini output\n' > "$tmp/repo/integrations/gemini-cli/agents/sample-agent.md" +printf 'Qwen output\n' > "$tmp/repo/integrations/qwen/agents/sample-agent.md" +# integrations/cursor is deliberately absent: cursor is the tool that fails. + +status=0 +(cd "$tmp/home/project" && HOME="$tmp/home" PATH=/usr/bin:/bin \ + bash "$tmp/repo/scripts/install.sh" --no-interactive --no-convert \ + --tool gemini-cli,cursor,qwen > "$tmp/output" 2>&1) || status=$? + +[[ "$status" -ne 0 ]] || fail "install exited 0 although cursor failed" +[[ -f "$tmp/home/.gemini/agents/sample-agent.md" ]] || fail "gemini-cli, before the failure, did not install" +[[ -f "$tmp/home/project/.qwen/agents/sample-agent.md" ]] \ + || fail "qwen, after the failing cursor, was never installed" +grep -q 'integrations/cursor missing' "$tmp/output" || fail "cursor's own error was not shown" +grep -q 'Failed: cursor' "$tmp/output" || fail "the summary does not name the failed tool" +grep -q 'Installed 2 of 3 tool(s)' "$tmp/output" || fail "the summary does not count the tools that installed" +echo "PASS: a failing tool is reported and the tools after it still install" + +# The clean path is unchanged: every tool installs and the install exits 0. +mkdir -p "$tmp/repo/integrations/cursor/rules" "$tmp/home2/project" +printf -- '---\ndescription: x\n---\nCursor output\n' > "$tmp/repo/integrations/cursor/rules/sample-agent.mdc" +(cd "$tmp/home2/project" && HOME="$tmp/home2" PATH=/usr/bin:/bin \ + bash "$tmp/repo/scripts/install.sh" --no-interactive --no-convert \ + --tool gemini-cli,cursor,qwen > "$tmp/output" 2>&1) || fail "a clean install exited non-zero" +grep -q 'Done! Installed 3 tool(s)' "$tmp/output" || fail "a clean install did not report all three tools" +echo "PASS: a clean multi-tool install still exits 0"