Skip to content

PLN-228: fix: improve install script error handling and diagnostics - #43

Merged
mikeangstadt merged 1 commit into
closedloop-ai:mainfrom
peterulsteen:symphony/pln-228
Apr 10, 2026
Merged

mikeangstadt merged 1 commit into
closedloop-ai:mainfrom
peterulsteen:symphony/pln-228

Conversation

@peterulsteen

@peterulsteen peterulsteen commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Improves install.sh to cover the full scope of PLN-228. Changes are confined to install.sh; no plugin files touched.

Error surfacing (AC-002, AC-004, AC-005)

  • Capture claude CLI stderr to per-run tempfiles under ${TMPDIR:-/tmp}/closedloop-install.XXXXXX and display the underlying error when marketplace registration or plugin install/update fails
  • sanitize_stderr strips ANSI color escapes and non-printable control characters using bash ANSI-C quoting ($'\033') plus tr octal ranges so it works under BSD sed (macOS) as well as GNU sed
  • Per-run work directory cleaned up via EXIT trap

Idempotent re-run messaging (AC-001, AC-006, AC-007, AC-008, AC-009, AC-010)

  • Marketplace pre-check via claude plugin marketplace list --json + jq — re-runs print Marketplace already registered: closedloop-ai instead of re-invoking marketplace add
  • Pre/post snapshots via claude plugin list --json + jq classify each plugin as one of three distinct states: Installed: <plugin>, Updated: <plugin> (<old> -> <new>), or Already up to date: <plugin>
  • Summary line reports all three counts separately (6 installed, 0 updated, 0 already up to date vs. 0 installed, 0 updated, 6 already up to date), preserving the failed count

Shell guard (AC-011)

  • POSIX-portable BASH_VERSION guard at the very top of the script, before any bash-only syntax, so invocations under dash/ash exit cleanly with a single-line guidance message pointing at bash install.sh or the piped-curl invocation
  • Also checks BASH_VERSINFO ≥ 3.2 so macOS's default bash still works

jq hard prereq (AC-012)

  • jq promoted from soft warn-and-continue to a hard preflight requirement (needed for the snapshot/idempotency logic). Missing jq prints brew install jq / apt install jq and exits before any marketplace or plugin operations run

Test plan

Verified end-to-end on macOS (bash 3.2, BSD sed):

  • Fresh install (marketplace and all 6 plugins removed) — all plugins install, 6× Installed: <plugin>, summary All 6 plugins ready (6 installed, 0 updated, 0 already up to date) (AC-001, AC-002, AC-003, AC-007, AC-008)
  • Idempotent re-run immediately after fresh install — Marketplace already registered: closedloop-ai, 6× Already up to date: <plugin>, summary All 6 plugins ready (0 installed, 0 updated, 6 already up to date); output visibly different from fresh run (AC-006, AC-007, AC-008, AC-009)
  • Simulated marketplace failure (bogus MARKETPLACE_SOURCE) — underlying claude CLI error surfaced to user (Failed to add marketplace: …HTTPS authentication failed…) instead of a generic warn line (AC-005)
  • Simulated plugin install+update failure (bogus entry nonexistent-plugin-xyz added to PLUGINS array) — both install and update attempts fail, sanitize_stderr surfaces the real CLI error (Failed to update plugin "nonexistent-plugin-xyz@closedloop-ai": Plugin "nonexistent-plugin-xyz" not found), warn fires, FAILED counter increments, summary shows 3 plugins processed: 0 installed, 0 updated, 2 already up to date, 1 failed (AC-003, AC-004)
  • Updated path with a version delta (pre-seeded SNAPSHOT_PRE with a fictitious 0.0.1 for bootstrap) — classification correctly emits Updated: bootstrap (0.0.1 -> 1.2.0), summary shows All 6 plugins ready (0 installed, 1 updated, 5 already up to date) (AC-007 Updated branch, AC-008)
  • dash install.sh — bash guard fires, exits 1 with the guidance message, no claude commands executed (AC-011)
  • jq hidden from PATH — preflight exits 1 with brew/apt install instructions before any marketplace or plugin operations run (AC-012)

Bugs uncovered and fixed during verification

  1. snapshot_version tripped set -e on an empty grep match inside the classification command substitution — fresh installs succeeded but the script errexited before printing any per-plugin status or the summary. Fixed with || true.
  2. The UP_TO_DATE branch only incremented the counter with no info line, violating AC-007's per-plugin message requirement. Fixed by emitting Already up to date: <plugin>.
  3. sanitize_stderr used GNU-sed-only \x1b / \x00-\x08 escape syntax and errored with RE error: invalid character range on BSD sed (macOS), silently swallowing the CLI errors AC-002/AC-004/AC-005 require to be shown. Rewritten to use a literal ESC via bash ANSI-C quoting plus tr with POSIX octal ranges.

🤖 Generated with Claude Code


Loop ID: 019d6e91-f371-75eb-84fa-93dfae76959e
Artifact: https://app.closedloop.ai/implementation-plans/PLN-228

@mikeangstadt

Copy link
Copy Markdown
Collaborator

bump build version and it LGTM!

…messaging, and shell guard

Rework install.sh so `curl | bash` failures produce actionable output,
re-runs give meaningful per-item feedback, and non-bash invocations
fail fast with guidance.

- Surface real `claude` CLI errors by capturing stderr to per-run
  tempfiles under `${TMPDIR:-/tmp}/closedloop-install.XXXXXX` (cleaned
  via an EXIT trap) and displaying them through a BSD- and GNU-sed
  portable `sanitize_stderr` helper (literal ESC via ANSI-C quoting
  plus POSIX `tr` octal ranges).
- Make the marketplace registration idempotent via
  `claude plugin marketplace list --json` — re-runs print
  "Marketplace already registered: closedloop-ai" instead of
  re-invoking `marketplace add`.
- Classify each plugin per run by diffing pre/post snapshots from
  `claude plugin list --json`, printing exactly one of `Installed:`,
  `Updated: <plugin> (<old> -> <new>)`, or `Already up to date:`, and
  rendering a three-count summary that preserves the failed count.
- Add a POSIX-portable `BASH_VERSION` guard at the very top of the
  script so `dash install.sh` (and similar) exit cleanly with a
  single-line guidance message before any bash-only syntax runs.
- Promote `jq` from a soft warn-and-continue to a hard preflight
  requirement with brew/apt install instructions, since the snapshot
  and idempotency logic depend on it.

Verified on macOS (bash 3.2, BSD sed): fresh install, idempotent
re-run, bogus marketplace source, invalid plugin ref, synthesized
`Updated` version delta, `dash install.sh`, and `jq` hidden from
PATH all produce the expected output and exit codes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mikeangstadt
mikeangstadt merged commit 154d804 into closedloop-ai:main Apr 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants