Automation
Hook
Hooks
What kraken runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
> /plugin marketplace add rafael-adcp/kraken > /plugin install kraken@kraken
Ships with kraken. Installing the plugin gets these hooks.
What fires, and when
SessionEnd
bash "${CLAUDE_PLUGIN_ROOT}/hooks/session-end-release.sh"
StopFailure
- Matches
rate_limitbash "${CLAUDE_PLUGIN_ROOT}/hooks/stop-failure-release.sh"
In the plugin's words
How kraken describes its own hook set.
An OPTIMIZATION over lease expiry, never the recovery mechanism: under kraken-protocol/9 a claim is a lease that frees itself within one TTL (PROTOCOL.md §5), and these hooks just hand it back sooner. SessionEnd (graceful exit) auto-releases so the task requeues in seconds
Where it lives
- hooks/lib-release-claims.shGitHub
Read the script
# lib-release-claims.sh — the shared claim-release loop behind the lifecycle # hooks (session-end-release.sh, stop-failure-release.sh) and the Copilot # ambush loop (scripts/kraken-loop.sh). Source it, then call # `release_all_claims "<reason>" [worker]`. Not a hook itself. # # Discovery: `kraken.py claim` writes $KRAKEN_STATE_DIR/claim-<worker>.json on a # won claim; deliver/escalate/release remove it. release_all_claims runs # `kraken.py release` for every claim-*.json still present (in practice one; the # glob also self-heals any straggler and needs no session->worker mapping). # # Best-effort: a failed release falls back to the lease expiring on its own # (PROTOCOL.md §5) — it never fails the caller. Everything here is an # OPTIMIZATION over that expiry, not a precondition for recovery. # CLAUDE_PLUGIN_ROOT is set when Claude Code runs a hook; the dirname fallback # keeps the scripts runnable standalone (tests). KRAKEN_HOOKS_ROOT="${CLAUDE_PLUGIN_ROOT:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"}" KRAKEN_BIN="$KRAKEN_HOOKS_ROOT/skills/unleash/kraken.py" KRAKEN_STATE="${KRAKEN_STATE_DIR:-$HOME/.kraken}" # Read a top-level string field out of a claim JSON — jq if present, else a # portable grep/sed fallback (the conformance suite keeps jq optional). kraken_json_field() { # $1 = file, $2 = field if command -v jq >/dev/null 2>&1; then jq -r --arg k "$2" '.[$k] // empty' "$1" 2>/dev/null else sed -n 's/.*"'"$2"'"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1" | head -1 fi } # release_all_claims REASON [WORKER] — run `kraken.py release` for every open # claim on this machine, or, with WORKER, only that worker's own claim (a loop # that knows its identity must never free a co-located worker's live claim). # The released marker lands before in-progress drops and the claim ref last — # the ordering that frees the lock honestly (PROTOCOL.md §9). release_all_claims() { local reason="$1" only="${2:-}" f repo issue worker [ -d "$KRAKEN_STATE" ] || return 0 shopt -s nullglob 2>/dev/null || true for f in "$KRAKEN_STATE"/claim-*.json; do [ -f "$f" ] || continue repo="$(kraken_json_field "$f" repo)" issue="$(kraken_json_field "$f" issue)" worker="$(kraken_json_field "$f" worker)" # Skip a malformed/empty file we cannot act on, never guess. [ -n "$repo" ] && [ -n "$issue" ] && [ -n "$worker" ] || continue [ -z "$only" ] || [ "$worker" = "$only" ] || continue python3 "$KRAKEN_BIN" release "$repo" "$issue" "$worker" "$reason" >/dev/null 2>&1 || true done return 0 } - hooks/session-end-release.shRunsGitHub
Read the script
#!/usr/bin/env bash # session-end-release.sh — the bundled SessionEnd hook (hooks.json). # # When a worker's session ends *gracefully* (terminal closed, /exit) while it # still holds a lease, this releases it via the shared loop in # lib-release-claims.sh, so the task returns to the queue in seconds instead of # at the end of the lease TTL. # # This is an OPTIMIZATION, never the recovery mechanism: under protocol/6 the # claim is a lease that expires on its own (PROTOCOL.md §5), so a session that # fires no hook at all — a hard kill, a crash, a harness with no hook events — # still frees its task within one TTL. Releasing early is simply more polite to # the next worker. Graceful end only: a usage limit does NOT end the session, so # SessionEnd never fires there — that path is the StopFailure hook's # (stop-failure-release.sh). See the #60 FAQ in README.md. # # Best-effort: ALWAYS exits 0 (a failed release just waits out the TTL); the # SessionEnd JSON on stdin is unused. set -u . "$(dirname "$0")/lib-release-claims.sh" release_all_claims "session ended" exit 0
- hooks/stop-failure-release.shRunsGitHub
Read the script
#!/usr/bin/env bash # stop-failure-release.sh — the bundled StopFailure hook (hooks.json, matcher # `rate_limit`). # # A usage limit kills the turn but not the session, so SessionEnd never fires and # a held lease would squat on in-progress until its TTL runs out — and the wake # the watcher spent on the dead turn would be lost (the queue snapshot no longer # changes). StopFailure is the one event that DOES fire there, so this hook: # 1. stamps the wake-retry flag ($KRAKEN_STATE_DIR/wake-retry); `kraken.py # watch` re-emits its wake when the flag is newer than its last emission, so # the drain resumes on its own once the limit window resets; # 2. releases every open claim on this machine (reason "usage limit"): the task # requeues in seconds, and this worker re-claims it after reset via the # retry wake and continues on the existing branch. # # Matcher is `rate_limit` only: a usage limit is account-wide, so any session's # rate_limit failure implies the worker sessions here are limited too — releasing # their claims is correct. Per-session error types could fire on a healthy worker # and must not release its live claim, so they are deliberately not matched. # # Best-effort: ALWAYS exits 0. gh still works at limit time, so the release # normally lands; if not, the lease expiry is the backstop — this hook makes # recovery immediate, it is not what makes it happen. The StopFailure JSON on # stdin is unused — the matcher already scoped the error type. set -u . "$(dirname "$0")/lib-release-claims.sh" # Flag first, release second: even with no claim held, the consumed wake must be # retried — and if the release fails, the retry still has to happen. mtime is the # signal the watcher reads; the timestamp content is for humans. mkdir -p "$KRAKEN_STATE" 2>/dev/null || true date -u +"%Y-%m-%dT%H:%M:%SZ" > "$KRAKEN_STATE/wake-retry" 2>/dev/null || true release_all_claims "usage limit" exit 0
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withkraken
You set the targets; the tentacles devour them. Unleash the Kraken. One head, many tentacles — a task queue built on GitHub Issues where named agent workers (Claude Code, GitHub Copilot CLI, or any tool that follows the protocol) claim tasks, execute them,
Get the whole plugin
Stats
4
Stars
0
Forks
Active
Maintenance
Python
Language
MIT
License
23h ago
Last commit
2mo ago
Created
Repo: rafael-adcp/kraken

