Run one Nazgul automation-heartbeat tick — triages the work inbox and auto-starts the next objective if idle. Opt-in and default-off; fired by an optional Claude Code native scheduled agent (routine) or run by hand. Use when asked to "run a heartbeat tick", "check the inbox", or to test/debug the heartbeat.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add OrodruinLabs/nazgul --skill heartbeat --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Heartbeat?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/orodruinlabs-heartbeat)More formats (shields.io, HTML) on the badges page.
---
name: nazgul:heartbeat
description: Run one Nazgul automation-heartbeat tick — triages the work inbox and auto-starts the next objective if idle. Opt-in and default-off; fired by an optional Claude Code native scheduled agent (routine) or run by hand. Use when asked to "run a heartbeat tick", "check the inbox", or to test/debug the heartbeat.
context: fork
allowed-tools: Read, Bash
metadata:
author: Jose Mejia
---
# Nazgul Heartbeat
## Examples
- `/nazgul:heartbeat` — Run one heartbeat tick by hand (identical to a scheduled routine firing)
## Current State
- Heartbeat enabled: !`jq -r '.automation.heartbeat.enabled // false' nazgul/config.json 2>/dev/null || echo "false"`
- Inbox dir: !`jq -r '.automation.heartbeat.inbox.dir // "nazgul/inbox"' nazgul/config.json 2>/dev/null || echo "nazgul/inbox"`
- Inbox candidates: !`ls -1 nazgul/inbox/*.md nazgul/inbox/*.json 2>/dev/null | wc -l | tr -d ' '`
- Latest heartbeat log: !`ls -1t nazgul/logs/heartbeat-*.jsonl 2>/dev/null | head -1 || echo "none yet"`
## Instructions
Run exactly one heartbeat tick and report the outcome. This skill is trigger-agnostic: run it by
hand for testing, or configure a Claude Code native scheduled agent (routine) to fire it on an
interval (see below).
### Step 1: Check Initialization
If `nazgul/config.json` does not exist:
- Output: "Nazgul not initialized. Run `/nazgul:init` first."
- Stop here.
### Step 2: Run One Tick
Invoke the tick engine directly — it is the entire implementation; this skill only wraps it:
```bash
NAZGUL_SESSION_ID="$(cat nazgul/.session_id 2>/dev/null)" bash scripts/heartbeat.sh
```
`NAZGUL_SESSION_ID` tells the tick which `nazgul/sessions/*.lock` is its OWN, so the stacking
pre-steps' serialization guard counts only OTHER sessions. `nazgul/.session_id` is the right source
and the env vars are not: `CLAUDE_SESSION_ID` is unset in a skill's bash environment, and
`CLAUDE_CODE_SESSION_ID` holds a subagent's own id rather than the registered one — whereas
`scripts/session-context.sh` persists the exact id it then registers the lock under. The variable is
optional (the engine falls back to the same file, then to a loud
`stack_skipped_session_ambiguity` decision record); it is passed here so the sanctioned path is
explicit rather than inferred.
`scripts/heartbeat.sh` is self-contained and trigger-agnostic: it gates on
`automation.heartbeat.enabled`, enforces the two unconditional hard stops (BLOCKED task, security
rejection) regardless of that flag, triages the inbox, and — only when idle and clear — archives the
picked candidate and auto-starts it. It always exits 0 and appends one decision record to
`nazgul/logs/heartbeat-<date>.jsonl`.
### Step 3: Report the Tick
Read the last line of the latest `nazgul/logs/heartbeat-<date>.jsonl` (`tail -1`) and report the
`decision` field with its context:
| `decision` | Report |
|---|---|
| `disabled` | Heartbeat is disabled (`automation.heartbeat.enabled: false`) — tick was a no-op |
| `hard_stop` | Halted — `reason` (`blocked_task` and/or `security_rejection`) |
| `nothing_actionable` | Saw N inbox candidates, none actionable |
| `skipped` | Saw N, picked `<picked>`, skipped (`reason`) |
| `started` | Saw N, picked `<picked>` → started `<objective>` (archived to `<archived_to>`) |
Use `/nazgul:log` for the full historical heartbeat timeline across ticks.
## Enabling the Interval Trigger (opt-in, default off)
`automation.heartbeat.enabled` defaults to `false` — an unconfigured project never ticks on its own.
To enable unattended, periodic ticking:
1. Set `automation.heartbeat.enabled: true` in `nazgul/config.json` (review
`automation.heartbeat.inbox.dir` and `automation.heartbeat.auto_start` for your setup).
2. Configure a Claude Code **native scheduled agent (routine)** to fire `/nazgul:heartbeat` on your
chosen interval (`automation.heartbeat.interval`, e.g. `30m`). Routines are a Claude Code platform
primitive independent of this plugin — set one up per the Claude Code scheduled-agent
documentation, pointing it at this skill.
3. Each fired tick is identical to running `/nazgul:heartbeat` by hand: same gates, same hard stops,
same decision record.
The core (`scripts/heartbeat.sh`) does not know or care what fired it — a routine, a human, or a test
harness all invoke the same tick. This skill wires no OS-level cron/launchd job and no `claude -p`
invocation; that remains deferred (FEAT-009).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!