ops-update — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ops-update (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Load ops-rules before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If AskUserQuestion / Workflow are missing, follow Rule 10 in ops-rules (Hermes: numbered options / two-turn Telegram card; delegate_task).
Upgrades the local claude-ops plugin to the newest version published in the ops-marketplace catalogue, then leaves the box clean: no stale cache dirs, no dangling version-pinned paths.
bin/ops-update-check runs daily from the ops daemon (update-check service) and answers one question: is a newer version published? It writes the verdict to ~/.claude/state/ops-update/update-available.json and exits 3 when an update exists, 0 when current.
It never installs anything. Detection and application are deliberately split: a background job that swapped the plugin out mid-session would break a working install at the worst possible moment. Applying is always ops-update, run on the user's word.
When you see that an update is available — because the state file says so, or because the user asks — surface it once and offer to apply it:
"${CLAUDE_PLUGIN_ROOT}/bin/ops-update-check" --json # current verdict, throttled to daily
"${CLAUDE_PLUGIN_ROOT}/bin/ops-update-check" --force # recheck now, ignoring the throttleThen a single AskUserQuestion: [Update now] [Show what changed] [Not now]. Only on Update now do you run bin/ops-update. Never chain the two, and never apply an update the user has not just agreed to in that exchange.
The daily cron is the only thing that ever ran the check, so a box without the daemon (Linux, or launchd never set up) heard about a new version exactly never. bin/ops-pretool-skill-update closes that: a PreToolUse hook on ^Skill$ re-uses the same throttled check whenever an ops skill is invoked, so the first skill call of the day says what the daily cron would have said.
Behaviour is set by auto_update in preferences.json (or $OPS_AUTO_UPDATE):
| Mode | What happens on a stale install |
|---|---|
off | nothing |
notify _(default)_ | the session is told it is behind, and to run /ops:ops-update |
auto | also runs bin/ops-update in the background, then asks for a reload |
auto is the one exception to the split above, and it stays opt-in for the reason stated there: the running session keeps the old version until it reloads, so the upgrade lands detached, behind a lock, and never prunes the tree the session is rooted in. Turning it on is the user's decision, not yours — never switch a box to auto on your own initiative.
Flags: --json (verdict on stdout), --no-fetch (compare against the catalogue already on disk, no network), --force (ignore the once-a-day throttle), --quiet (write state, print nothing — how the daemon runs it). Override the cadence with $OPS_UPDATE_CHECK_INTERVAL in seconds.
The workhorse is `${CLAUDE_PLUGIN_ROOT}/bin/ops-update`. It runs a 9-step loop:
claude plugin marketplace update ops-marketplace (git-pulls the clone).marketplace.json (or --to X.Y.Z).claude plugin update ops@ops-marketplace, with a force-reinstall fallback (rm cache + claude plugin install) for the Claude Code bug where update reports "already latest" while the cache stays stale (anthropics/claude-code#61954).scripts/cache-patches/ against the new cache (empty when all fixes are upstream — the desired state).cache/ops-marketplace/ops/<ver>/ except the new one.cache/.../ops/<oldver>/ paths in live configs/scripts/systemd units only (never logs, memory, or transcripts — those use ${CLAUDE_PLUGIN_ROOT} at runtime so they self-resolve).ops-post-update-migrate (idempotent, per-version). It also maintains a stable cache/.../ops/current/ directory (rsynced from the new version and repointed in installed_plugins.json) so Claude Code GC'ing the old versioned dir mid-session never causes "Plugin directory does not exist" hook errors.~/Projects, fast-forwards its main to origin/main so a dev clone never silently drifts behind the published release. Acts only on a clean main (never clobbers uncommitted WIP, a feature branch, or unpushed commits); a no-op when no checkout exists. Skip with --no-localsync./reload-plugins is needed to load it.bin/ops-update step 9 runs scripts/install-companions.shagainst plugin-dependencies.json. Every companion with required: true is co-installed when missing and updated on every ops-update:
/ops:desktop + captcha cascade/ops:flow project mode, /ops:projects, /ops:go/ops:flow ad-hoc (/spec /review /qa /ship)/ops:ops-feature-devSkip only with --no-companions or OPS_SKIP_COMPANIONS=1.
# manual companion pass
bash "${CLAUDE_PLUGIN_ROOT}/scripts/install-companions.sh"
bash "${CLAUDE_PLUGIN_ROOT}/scripts/install-companions.sh --status"
# skip from ops-update:
${CLAUDE_PLUGIN_ROOT}/bin/ops-update --no-companionsSteps 5–6 are destructive (prune + rewrite), so always dry-run first, show the plan, confirm, then apply (Rule 5).
${CLAUDE_PLUGIN_ROOT}/bin/ops-update --dry-runPresent the output: current → target version, which cache versions would be pruned, which files would be rewritten. If the dry-run shows already on <ver> and nothing to prune/rewrite, tell the user the box is already current and stop (offer --force only if they suspect a stale cache).
"already on <ver>" is a lie when the marketplace clone is stuck. Step 1 prints ✓ marketplace catalogue refreshed even when the underlying git pull silently did nothing, so step 2 resolves a stale target and the whole run no-ops. ~/.claude/plugins/marketplaces/ops-marketplace is a real checkout of this repo; a dirty worktree (a local edit, stray .bak files) blocks the fast-forward. Verified 2026-09-05: it sat 28 commits behind on v3.10.3 while v3.10.5 was published, and the update reported the box current.
Whenever the target version does not match what you just released, check the clone before reaching for --force:
cd ~/.claude/plugins/marketplaces/ops-marketplace
git fetch -q origin
git status -sb # ahead/behind AND porcelain lines
grep -o '"version": *"[^"]*"' .claude-plugin/marketplace.json | head -1Repair: diff each modified file against origin/main first — a local edit that is byte-identical to upstream is safe to drop, anything else is real work that must be salvaged before you touch it. Then clean the tree, git merge --ff-only origin/main, and re-run the dry-run. The target version should now be the published one.
Use AskUserQuestion before applying:
Upgrade local claude-ops <CUR> → <NEW>? (prunes N old cache versions, rewrites M files)
[Apply upgrade]
[Force re-materialise cache] ← only if same-version stale-cache is suspected
[Cancel]${CLAUDE_PLUGIN_ROOT}/bin/ops-update # or: --forceStream the step-by-step output. On success, surface the final line verbatim:
Restart Claude Code (or run `/reload-plugins`) to load v<NEW>.
The running session will NOT see the new version until reload — this is a Claude Code constraint, not a failure.
| Flag | Effect |
|---|---|
--dry-run | Report only; change nothing. Always run this first. |
--force | Force-reinstall even when the CLI claims "already latest" (bug #61954). |
--to X.Y.Z | Target a specific version instead of the catalogue's newest. |
--no-prune | Keep old cache versions. |
--no-patches | Skip the cache-patch reapply step. |
--no-rewrite | Skip the stale-version-path rewrite step. |
--no-localsync | Skip fast-forwarding a linked local source checkout's main. |
--no-companions | Skip required companion co-install/update (desktop-act, gsd, gstack, superpowers, feature-dev). |
The bin auto-detects a non-TTY and drops colour; its output is already line-per-fact, so relay it as-is — no tables, no banners.
"already on <ver>" → nothing to prune/rewrite/migrate).
$HOME/.claude/pluginsstate; it writes no personal data.
${CLAUDE_PLUGIN_ROOT}/bin/ops-release(bumps plugin.json + marketplace.json + CHANGELOG, opens the release PR, tags). ops-release ships it; ops-update pulls it down locally.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.