---
description: "Update the pipeline to the latest published npm release: registry check, npm pack, install, migrate. Use when the installed pipeline is behind and should be brought to the latest version."
description-tr: "Pipeline'ı npm'deki son yayına günceller: registry kontrolü, npm pack, install, migrate."
allowed-tools: Bash, Read, Write, AskUserQuestion
---

# multi-agent update

Update the pipeline in one command. The npm registry is the single update channel: the latest published release is downloaded and installed. Existing preferences are preserved; only skill / script / schema files are refreshed.

A git clone of the pipeline repo is a maintainer workspace, kept in sync by `/multi-agent:sync` - it is never consulted here. A fix that only exists on `main` reaches users when a release is published, not before.

## Steps

1. **Resolve the package and both versions** (run steps 1-3 in ONE shell block so the variables survive):
   ```bash
   PKG="@{npm-scope}/multi-agent-pipeline"
   REG="https://registry.npmjs.org"
   CUR=$(tr -d '[:space:]' < "$HOME/.claude/.pipeline-version" 2>/dev/null)
   [ -n "$CUR" ] || CUR="unknown"
   # Read the registry directly - `npm view` can answer from a stale local cache.
   LATEST=$(curl -fsS "$REG/$(printf '%s' "$PKG" | sed 's|/|%2F|')/latest" \
     | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8")).version' 2>/dev/null)
   if [ -z "$LATEST" ]; then
     echo "Registry unreachable ($REG) - check your network and retry."
     exit 1
   fi
   # dist-tags.required is the supported-version floor (absent on most releases).
   REQUIRED=$(curl -fsS -H 'Accept: application/vnd.npm.install-v1+json' \
     "$REG/$(printf '%s' "$PKG" | sed 's|/|%2F|')" \
     | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8"))["dist-tags"].required || ""' 2>/dev/null)
   echo "Current: v$CUR   Latest: v$LATEST${REQUIRED:+   Required floor: v$REQUIRED}"
   ```

   This command is never blocked by the floor  -  it is the remedy for it. The
   line above is where the floor is reported, so the user sees why a run halted;
   `$REQUIRED` does not survive into the later shell blocks.

2. **Stop early when already current** (still refresh the marketplace, step 4b):
   ```bash
   [ "$CUR" = "$LATEST" ] && echo "✓ Already up to date: v$LATEST"
   ```

3. **Download the release and install**:
   ```bash
   UPD_DIR="${TMPDIR:-/tmp}/multi-agent-update"
   rm -rf "$UPD_DIR" && mkdir -p "$UPD_DIR"
   # `--registry` does NOT override a scope mapping. A user-level .npmrc line
   # like `@scope:registry=https://npm.pkg.github.com` wins over it, so the
   # download hits the wrong registry, fails with `notarget`, and `--silent`
   # hides why. Pin the SCOPE, which is the setting that actually loses.
   SCOPE=$(printf '%s' "$PKG" | sed -n 's|^@\([^/]*\)/.*|\1|p')
   PACK_OUT=$(npm pack "$PKG@$LATEST" ${SCOPE:+"--@${SCOPE}:registry=$REG"} \
     --registry "$REG" --pack-destination "$UPD_DIR" 2>&1) || {
     echo "Download failed for $PKG@$LATEST:"; printf '%s\n' "$PACK_OUT"
     echo "If this says 'notarget', an .npmrc scope mapping is routing @${SCOPE} elsewhere:"
     echo "  npm config get @${SCOPE}:registry"
     exit 1
   }
   # Never glob into tar: an older tarball left in $UPD_DIR would be extracted
   # instead, and the run would install the version it was trying to leave.
   TGZ="$UPD_DIR/$(printf '%s' "$PACK_OUT" | tail -1 | tr -d '[:space:]')"
   [ -f "$TGZ" ] || { echo "npm pack reported no tarball; aborting."; exit 1; }
   tar -xzf "$TGZ" -C "$UPD_DIR"
   node "$UPD_DIR/package/install.js" --all
   ```

   The installer refreshes every configured CLI target (Claude Code, Copilot CLI, Codex CLI) and writes the new version to `$HOME/.claude/.pipeline-version`.

4b. **Update the stack-plugin marketplace** (pulls the latest plugin versions):
   ```bash
   # Stack skills ship as versioned plugins in {owner}/multi-agent-plugins.
   # Refresh the marketplace so newly published plugin versions are picked up.
   if claude marketplace list 2>/dev/null | grep -q "multi-agent-plugins"; then
     claude marketplace update multi-agent-plugins 2>/dev/null \
       || claude marketplace add {owner}/multi-agent-plugins 2>/dev/null
     echo "  -> marketplace multi-agent-plugins refreshed"
   else
     echo "  -> marketplace not added yet; add with: claude marketplace add {owner}/multi-agent-plugins"
   fi
   ```

4c. **Prune retired adapter files** (Cursor / Antigravity / Copilot Chat were removed in v10.7.0 - the pipeline targets Claude Code, Copilot CLI and Codex CLI):
   ```bash
   # Per-project adapter files (.cursor/, .agent/, .github/copilot-instructions.md)
   # live in your repos and are left untouched - remove them manually if you like.
   echo "  -> no global adapter files to prune"
   ```

   > **Do NOT delete `$HOME/.codex/prompts/multi-agent.md`.** Releases up to
   > v12.11.0 removed it here as a retired v9.7.0 adapter leftover. Codex CLI is a
   > supported target again as of v13.0.0 and the installer writes that file, so
   > deleting it silently breaks the `/multi-agent` slash command on Codex.

5. **Migrate preferences** (if there is an old schema):
   ```bash
   if [ -f "$HOME/.claude/scripts/migrate-prefs.mjs" ]; then
     node "$HOME/.claude/scripts/migrate-prefs.mjs"
   fi
   ```

5b. **Auto-configure operational reporting.** Off means silent: the emitter still
   no-ops unless `enabled` is true AND a token resolves. This step never SHIPS a
   secret; when no token is onboarded it REQUESTS a per-machine write-only token
   from the reporting endpoint's `/register` route (self-registration, v15.8.0+),
   stores it only in the credential store, and flips `usageLog.enabled` on. It
   reports coarse run metadata only - never prompts, code, diffs, or paths.
   Resolution order: env `MULTI_AGENT_USAGE_TOKEN`, then `usageLog.token`, then
   the Keychain item named by `keychainMapping.usage_ingest`, then
   self-registration. Registration failing (offline, endpoint down, admin turned
   ingest off) leaves reporting off with one status line - never an error.
   **Opt-out is `usageLog.optOut: true`**: it blocks both the auto-enable and the
   self-registration permanently; print the opt-out hint on first auto-enable.
   ```bash
   PREFS="$HOME/.claude/multi-agent-preferences.json"
   # Reads go through node, not jq. node is a declared engine (>=20.11) so it
   # is always there; jq is not, and gating this block on it meant a machine
   # without jq silently never registered and never reported - which reads in
   # the panel exactly like nobody using the pipeline.
   if [ -f "$PREFS" ]; then
     pref() { node -e 'const fs=require("fs");let v;try{v=process.argv[2].split(".").reduce((a,k)=>a?.[k],JSON.parse(fs.readFileSync(process.argv[1],"utf8")))}catch{};process.stdout.write(v==null?"":String(v))' "$PREFS" "$1" 2>/dev/null; }
     ENABLED=$(pref global.usageLog.enabled)
     OPTOUT=$(pref global.usageLog.optOut)
     if [ "$ENABLED" != "true" ] && [ "$OPTOUT" != "true" ]; then
       TOK="${MULTI_AGENT_USAGE_TOKEN:-}"
       [ -z "$TOK" ] && TOK=$(pref global.usageLog.token)
       if [ -z "$TOK" ]; then
         KNAME=$(pref global.keychainMapping.usage_ingest)
         [ -n "$KNAME" ] && TOK=$(bash "$HOME/.claude/lib/credential-store.sh" get "$KNAME" 2>/dev/null)
       fi
       if [ -z "$TOK" ]; then
         EP=$(pref global.usageLog.endpoint); [ -z "$EP" ] && EP="https://mmerterden.vercel.app/api/usage/ingest"
         REG_EP="${EP%/ingest}/register"
         # Telemetry identity is the GitHub account name, never the git
         # identity.name (which can carry a corporate title). username -> live
         # gh login -> OS user.
         RUSER=$(pref global.identities.0.username)
         [ -z "$RUSER" ] && RUSER=$(gh api user --jq .login 2>/dev/null || echo "")
         [ -z "$RUSER" ] && RUSER="$USER"
         RESP=$(curl -sSL -m 10 -X POST -H "Content-Type: application/json" \
           --data "{\"u\":\"$RUSER\",\"c\":\"$(hostname -s 2>/dev/null || echo unknown)\"}" \
           "$REG_EP" 2>/dev/null)
         TOK=$(printf '%s' "$RESP" | node -e 'let b="";process.stdin.on("data",d=>b+=d).on("end",()=>{try{process.stdout.write(String(JSON.parse(b).token||""))}catch{}})' 2>/dev/null)
         if [ -n "$TOK" ]; then
           printf '%s' "$TOK" | bash "$HOME/.claude/lib/credential-store.sh" set "${USER}_Usage_Ingest_Token" "$(cat)"
           node -e 'const fs=require("fs"),p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));j.global=j.global||{};j.global.keychainMapping=j.global.keychainMapping||{};j.global.keychainMapping.usage_ingest=process.argv[2];fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");' "$PREFS" "${USER}_Usage_Ingest_Token"
           echo "  -> operational reporting: registered this machine (write-only token in credential store)"
           echo "     opt out any time: set global.usageLog.optOut=true in multi-agent-preferences.json"
         fi
       fi
       if [ -n "$TOK" ]; then
         node -e 'const fs=require("fs"),p=process.argv[1];const j=JSON.parse(fs.readFileSync(p,"utf8"));j.global=j.global||{};j.global.usageLog=j.global.usageLog||{};j.global.usageLog.enabled=true;fs.writeFileSync(p,JSON.stringify(j,null,2)+"\n");' "$PREFS"
         echo "  -> operational reporting configured"
       else
         echo "  -> operational reporting left off (no token; registration unreachable or disabled)"
       fi
     fi
   fi
   ```

6. **Show the new version and its changes** (from the packaged CHANGELOG - there is no git history on this channel):
   ```bash
   NEW=$(tr -d '[:space:]' < "$HOME/.claude/.pipeline-version" 2>/dev/null)
   echo ""
   echo "✓ Updated: v$CUR → v$NEW"
   echo ""
   echo "Changes:"
   awk -v new="## [$NEW]" -v cur="## [$CUR]" \
     'index($0,new){on=1} on&&index($0,cur){exit} on' \
     "$UPD_DIR/package/CHANGELOG.md" | head -40
   ```

7. **Smoke test (optional)** - the release tarball ships exactly two smokes for this purpose:
   ```bash
   echo ""
   echo "Verification:"
   bash "$UPD_DIR/package/pipeline/scripts/smoke-schema-validation.sh" 2>&1 | tail -1
   bash "$UPD_DIR/package/pipeline/scripts/smoke-cross-cli-behavior.sh" 2>&1 | tail -1
   rm -rf "$UPD_DIR"
   ```

## Output

```
Current: v15.6.0   Latest: v15.6.1
  -> npm pack @{npm-scope}/multi-agent-pipeline@15.6.1
  -> node install.js --all (53 commands, 263 scripts, 210 skills)
  -> migrate-prefs.mjs (0 changes  -  already v2.6.0)

✓ Updated: v15.6.0 → v15.6.1

Changes:
  ## [15.6.1] - 2026-08-19
  ### Fixed
  - Usage report reads the tracker as it is actually written
  ...

Verification:
  schema-validation: 27/27 pass
  cross-cli-behavior: 14/14 pass
```
