Claude-ops ops-daemon
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.
install
source · Clone the upstream repo
git clone https://github.com/Lifecycle-Innovations-Limited/claude-ops
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Lifecycle-Innovations-Limited/claude-ops "$T" && mkdir -p ~/.claude/skills && cp -r "$T/claude-ops/skills/ops-daemon" ~/.claude/skills/lifecycle-innovations-limited-claude-ops-ops-daemon && rm -rf "$T"
manifest:
claude-ops/skills/ops-daemon/SKILL.mdsource content
Runtime Context
Before diagnosing, load:
- Plugin root:
— newest installed versionecho "${CLAUDE_PLUGIN_ROOT:-$(ls -d "$HOME/.claude/plugins/cache/ops-marketplace/ops"/*/ 2>/dev/null | sort -V | tail -1)}" - Daemon health:
— primary diagnostic inputcat ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/daemon-health.json - Services config:
— per-service command + cron definitionscat ${CLAUDE_PLUGIN_DATA_DIR}/daemon-services.json - OS:
— daemon install is macOS-only (launchd). Linux/WSL/Windows fall back to manual invocation.uname -s
OPS ► DAEMON
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.
CLI/API Reference
bin/ops-daemon-manager.sh
| Command | Usage | Output |
|---|---|---|
| Emit JSON snapshot | |
| First-time install (idempotent) | Writes plist, loads launchd |
| Re-point plist at current PLUGIN_ROOT + reload | Fixes stale version paths |
| Unload + reload without reconfiguring | Clears stuck state |
| 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.
Health file schema
${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", "pid": <int|null>, "last_health": "<string|null>", "last_run": "<ISO-8601|empty>", "next_run": "<ISO-8601|empty>", "restarts": <int> } }, "action_needed": null | {"kind": "...", "service": "...", "message": "..."} }
A healthy daemon refreshes this file every 30s. An
mtime older than 120s is a strong fail signal.
Your task
Route on the first argument:
| Argument | Action |
|---|---|
(default) | Run all diagnostics, print a colored report, exit 0 if green / 1 otherwise |
| Run , then per detected issue ask the user for confirmation and apply the fix |
| Call |
| Print the JSON output of verbatim — consumed by other skills |
| Ask / via , then call the manager |
Diagnostic checklist
Run each check and track results as
pass / fail / warn:
- Plugin root resolved —
env var set ORCLAUDE_PLUGIN_ROOT
exists.~/.claude/plugins/cache/ops-marketplace/ops/<version>/scripts/ops-daemon.sh - OS supported —
isuname -s
. On Linux/WSL print the manual invocation and exit 0 with aDarwin
note. On native Windows print "not supported".warn - Plist installed —
exists.~/Library/LaunchAgents/com.claude-ops.daemon.plist - Plist points at current version — the second
inside<string>
equalsProgramArguments
. Mismatch = stale after upgrade (the most common failure mode).${PLUGIN_ROOT}/scripts/ops-daemon.sh - Plist is valid XML —
passes.plutil -lint - Launchctl registered —
shows the label with a real PID (notlaunchctl list
).- - Process alive —
succeeds.kill -0 <pid> - Bash binary exists — the first
in<string>
is executable and reportsProgramArguments
(required forBASH_VERSINFO >= 4
in the daemon script).declare -A - Health file fresh —
exists,daemon-health.json
within last 120 seconds.mtime - Every service has a command — iterate
services; each enabled entry must have a non-emptydaemon-services.json
field. Missingcommand
silently skips the service (historical bug).command - Running services alive — for each service in the health file with
, verifystatus=running|polling
succeeds.kill -0 <pid> - Cron services have future
—next_run
services must have ascheduled
timestamp in the future.next_run - wacli-sync path resolves — if enabled,
exists and is fresh. (Optional — mark warn not fail if missing.)~/.wacli/.health - No zombie children — no orphaned
orops-message-listener.sh
processes without a parentwacli-keepalive.sh
.ops-daemon.sh
Fix playbook
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 | | Yes — unloads + reloads |
| Plist missing | | No |
| Plist invalid XML | Regenerate via (after backup) | Yes — overwrites |
| Process dead but plist ok | | Yes — restarts |
| Health file stale (>120s) | | Yes |
Service missing | Merge from into user's after showing a diff | Yes — writes config |
| Bash binary missing/<4 | on macOS; on Linux check version; ask user to install | No (reports only) |
| Zombie child processes | with per-process confirmation (Rule 5) | Yes |
| Services config corrupt JSON | Restore from after confirmation + backup | Yes |
Never batch fixes. Per Rule 5, each destructive action needs its own
AskUserQuestion with [Apply] / [Skip] options.
Output format for check
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.
Output format for status
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.
Output format for fix
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.
Cross-OS notes
- macOS: full support via launchd. All subcommands available.
- Linux / WSL:
exitsops-daemon-manager.sh install
(69) and prints the manualEX_UNAVAILABLE
invocation.nohup
still validates the daemon script and services config.check - Windows native: unsupported. Use WSL.
Do not hardcode
launchctl in this SKILL — always route through the manager script so future systemd / Task Scheduler support is a one-line addition.
Examples
# 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'