From ops
Check claude-ops background daemon end-to-end and auto-fix common issues. Detects stale plist paths after plugin upgrades, missing service commands, dead processes, corrupt health files, and bash version mismatches.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ops:ops-daemon [check|fix|restart|status|uninstall][check|fix|restart|status|uninstall]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Before diagnosing, load:
Before diagnosing, load:
echo "${CLAUDE_PLUGIN_ROOT:-$(ls -d "$HOME/.claude/plugins/cache/ops-marketplace/ops"/*/ 2>/dev/null | sort -V | tail -1)}" — newest installed versioncat ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/daemon-health.json — primary diagnostic inputcat ${CLAUDE_PLUGIN_DATA_DIR}/daemon-services.json — per-service command + cron definitionsuname -s — daemon install is macOS-only (launchd). Linux/WSL/Windows fall back to manual invocation.Diagnostic + auto-fix surface for the background ops-daemon process. Acts like ops-doctor but scoped to the one subsystem users actually see break: the launchd daemon that keeps briefing-pre-warm, memory-extractor, message-listener, inbox-digest, and competitor-intel alive.
| Command | Usage | Output |
|---|---|---|
${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon-manager.sh status | Emit JSON snapshot | {os, installed, running, pid, plist_version_match, health_fresh, ...} |
${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon-manager.sh install | First-time install (idempotent) | Writes plist, loads launchd |
${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon-manager.sh upgrade | Re-point plist at current PLUGIN_ROOT + reload | Fixes stale version paths |
${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon-manager.sh restart | Unload + reload without reconfiguring | Clears stuck state |
${CLAUDE_PLUGIN_ROOT}/scripts/ops-daemon-manager.sh uninstall | Stop + remove plist | Returns system to pre-install state |
Accepts --plugin-root PATH to override auto-detection and --dry-run to preview without side effects.
${CLAUDE_PLUGIN_DATA_DIR}/daemon-health.json:
{
"timestamp": "<ISO-8601 UTC>",
"pid": <int>,
"uptime_seconds": <int>,
"services": {
"<name>": {
"status": "running|polling|scheduled|dead|needs_reauth|max_restarts_exceeded",
"pid": <int|null>,
"last_health": "<string|null>",
"last_run": "<ISO-8601|empty>",
"next_run": "<ISO-8601|empty>",
"restarts": <int>,
"latency_ms": <int>,
"last_success": "<ISO-8601|empty>",
"error_count": <int>,
"last_error": "<string|null>"
}
},
"action_needed": null | {"kind": "...", "service": "...", "message": "..."},
"credential_warnings": ["<string>", ...],
"rate_limit_warnings": ["<string>", ...]
}
A healthy daemon refreshes this file every 30s. An mtime older than 120s is a strong fail signal.
Per-service fields (Phase 16 additions):
latency_ms — duration of the most recent run for warm/cron services, in milliseconds. 0 when no run has occurred yet.last_success — ISO-8601 UTC of the last clean run (no errors). Empty until the first success.error_count — cumulative error count since daemon start. Reset on daemon restart.last_error — truncated (≤300 chars) message from the most recent failure. null when there have been no failures.Top-level fields (Phase 16 additions):
credential_warnings — list of credentials that are expiring within 7 days or are keys older than 180 days. Populated from offline inspection of preferences.json (never a live API call).rate_limit_warnings — list of integrations currently at ≥80% of their quota window. Populated from rate-limits.json counters.Persistent service failure: a service that hits max_restarts_exceeded status dispatches a one-shot HIGH severity push notification via ops-notify.sh to every configured sink (Telegram / Discord / ntfy / Pushover / macOS). The notification is re-armed the moment the service returns to running, so subsequent regressions still notify.
Cache timestamp files (.briefing_ts, .projects_ts, .marketing_ts, .prs_ts, .ci_ts) are deleted on local git commit and git merge so the next daemon warm cycle refreshes those specific caches without waiting for the normal throttle window.
Install:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/ops-install-git-hooks.sh
Preview without writing:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/ops-install-git-hooks.sh --dry-run
Remove:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/ops-install-git-hooks.sh --uninstall
The installer is idempotent and only touches the block between # BEGIN ops-daemon-invalidate and # END ops-daemon-invalidate — existing husky / lefthook / pre-commit content in .git/hooks/post-commit or .git/hooks/post-merge is preserved.
The opt-in marketing-prewarm cron service pre-fetches cross-platform marketing data (Klaviyo, Meta Ads, GA4, GSC, Google Ads) every 15 minutes so /ops:marketing loads instantly from cache. Disabled by default; enable with /ops:setup marketing once marketing credentials are configured.
Per-integration counters in ${CLAUDE_PLUGIN_DATA_DIR}/rate-limits.json — {quota, calls, window_start, warned_80pct} per integration. On each tracked API call the daemon advances the rolling window, increments the counter, and sends a single MEDIUM push notification when the ratio crosses 80%. Windows reset automatically on rollover, which re-arms the 80% warning.
Override the seeded defaults by setting rate_limit_quotas in ${CLAUDE_PLUGIN_DATA_DIR}/preferences.json:
{
"rate_limit_quotas": {
"meta_ads": { "quota": 200, "window_seconds": 3600 },
"klaviyo": { "quota": 75, "window_seconds": 60 }
}
}
The daemon checks ${CLAUDE_PLUGIN_DATA_DIR}/preferences.json once an hour for:
<integration>_expires_at — ISO-8601 timestamp or the literal string "never". Warns when within 7 days of expiry.<integration>_created_at — ISO-8601 timestamp of when an API key was last created/rotated. Warns once the key is 180 days old.The check is entirely offline — it never makes a network call. Warnings surface in daemon-health.json as credential_warnings and trigger a MEDIUM push notification once per (credential, day) tuple. /ops:fires lists them alongside Sentry / infra / CI issues.
Route on the first argument:
| Argument | Action |
|---|---|
check (default) | Run all diagnostics, print a colored report, exit 0 if green / 1 otherwise |
fix | Run check, then per detected issue ask the user for confirmation and apply the fix |
restart | Call ops-daemon-manager.sh restart |
status | Print the JSON output of ops-daemon-manager.sh status verbatim — consumed by other skills |
uninstall | Ask [Uninstall] / [Cancel] via AskUserQuestion, then call the manager |
Run each check and track results as pass / fail / warn:
CLAUDE_PLUGIN_ROOT env var set OR ~/.claude/plugins/cache/ops-marketplace/ops/<version>/scripts/ops-daemon.sh exists.uname -s is Darwin. On Linux/WSL print the manual invocation and exit 0 with a warn note. On native Windows print "not supported".~/Library/LaunchAgents/com.claude-ops.daemon.plist exists.<string> inside ProgramArguments equals ${PLUGIN_ROOT}/scripts/ops-daemon.sh. Mismatch = stale after upgrade (the most common failure mode).plutil -lint passes.launchctl list shows the label with a real PID (not -).kill -0 <pid> succeeds.<string> in ProgramArguments is executable and reports BASH_VERSINFO >= 4 (required for declare -A in the daemon script).daemon-health.json exists, mtime within last 120 seconds.daemon-services.json services; each enabled entry must have a non-empty command field. Missing command silently skips the service (historical bug).status=running|polling, verify kill -0 <pid> succeeds.next_run — scheduled services must have a next_run timestamp in the future.lsof -i :8080 | grep LISTEN returns a result. (Optional — mark warn not fail if missing.)ops-message-listener.sh processes without a parent ops-daemon.sh.For each failed check, fix mode proposes a specific repair and asks the user with AskUserQuestion (max 4 options — always include [Skip]):
| Failure | Fix | Destructive? |
|---|---|---|
| Plist stale version path | ops-daemon-manager.sh upgrade | Yes — unloads + reloads |
| Plist missing | ops-daemon-manager.sh install | No |
| Plist invalid XML | Regenerate via install (after backup) | Yes — overwrites |
| Process dead but plist ok | ops-daemon-manager.sh restart | Yes — restarts |
| Health file stale (>120s) | ops-daemon-manager.sh restart | Yes |
Service missing command | Merge from scripts/daemon-services.example.json into user's daemon-services.json after showing a diff | Yes — writes config |
| Bash binary missing/<4 | brew install bash on macOS; on Linux check $(command -v bash) version; ask user to install | No (reports only) |
| Zombie child processes | kill <pid> with per-process confirmation (Rule 5) | Yes |
| Services config corrupt JSON | Restore from scripts/daemon-services.default.json after confirmation + backup | Yes |
Never batch fixes. Per Rule 5, each destructive action needs its own AskUserQuestion with [Apply] / [Skip] options.
check━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OPS ► DAEMON CHECK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OS: macos
Plugin root: ${CLAUDE_PLUGIN_ROOT}
Daemon PID: 57004
Uptime: 1h 12m
✓ Plist installed
✓ Plist points at current version
✓ Plist is valid XML
✓ Launchctl registered, PID alive
✓ Bash binary found (5.3)
✓ Health file fresh (mtime 23s ago)
✓ All 5 enabled services have commands
✓ Running services alive
✓ Cron services have future next_run
STATUS: GREEN — daemon healthy
On failure, replace ✓ with ✗ and append a one-line remediation hint. Exit 1 so /ops:ops-status can surface red.
statusPrint the JSON from ops-daemon-manager.sh status verbatim. No wrapping. This is the machine-readable contract consumed by ops-status, ops-go, and other skills.
fixRender the check report, then for each failing check enter a confirmation loop:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OPS ► DAEMON FIX — 3 issues found
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✗ Plist points at old version 1.0.0
→ Proposed: ops-daemon-manager.sh upgrade
Then AskUserQuestion with [Apply fix] / [Skip this issue] / [Cancel all]. Repeat for each issue. After all actions, re-run check and print a before/after diff.
ops-daemon-manager.sh install exits EX_UNAVAILABLE (69) and prints the manual nohup invocation. check still validates the daemon script and services config.Do not hardcode launchctl in this SKILL — always route through the manager script so future systemd / Task Scheduler support is a one-line addition.
# Morning habit: confirm the daemon survived overnight
/ops:daemon check
# After a plugin upgrade (`/plugin upgrade claude-ops`):
/ops:daemon fix
# → detects stale plist, asks [Apply upgrade], reloads, verifies
# Embedded in another skill:
/ops:daemon status | jq -r '.health_fresh'
npx claudepluginhub lifecycle-innovations-limited/claude-ops --plugin opsHealth check and auto-repair for the ops plugin. Diagnoses manifest errors, broken permissions, invalid configs, stale caches, and missing files — then spawns an agent to fix everything automatically.
Runs environment diagnostics across 11 categories to detect misconfigured providers, auth issues, stale state, and broken hooks. Useful before demos or after plugin updates.
OrchestKit doctor for health diagnostics across manifest integrity, hook configuration, skill validation, agent frontmatter, MCP server connectivity, CC version compatibility, and permission rules. Reports issues with severity levels and auto-remediation suggestions. Validates component counts, detects orphaned entries, and checks CC version matrix compliance. Use when diagnosing plugin health, troubleshooting configuration issues, or running pre-release checks.