AgentStack
SKILL verified MIT Self-run

Dispatch Watcher Claude

skill-zrozoom-agent-skills-core-dispatch-watcher-claude · by ZroZoom

Claude Code adapter for the shared agent-presence protocol (for Codex CLI use `.agent/skills/dispatch-watcher/SKILL.md`, for Gemini CLI use `.agent/skills/dispatch-watcher-gemini/SKILL.md`). Use on Claude session start to run pre-flight, claim Slack-dispatched issues (@claude #N), and emit lifecycle events. Triggers: Claude session start, /dispatch, /agent-presence, message addressed to @claude i…

No reviews yet
0 installs
13 views
0.0% view→install

Install

$ agentstack add skill-zrozoom-agent-skills-core-dispatch-watcher-claude

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Dispatch Watcher Claude? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Dispatch Watcher Skill — Claude Code Adapter

This is the Claude Code-specific adapter for the shared agent-presence protocol. All shared logic (pre-flight, claim, lease, roster, takeover, presence event format) lives in .agent/skills/agent-presence/SKILL.md and scripts/agent-presence-helpers.sh — this file only contains Claude-specific glue.

When this Claude Code session starts (or when explicitly triggered by @claude # in ``), follow the workflow below.

Contract

  • Input: environment variables AGENT_ID (must start with claude-),

GH_TOKEN_CLAUDE_BOT, SLACK_BOT_TOKEN, ROSTER_MESSAGE_TS, ROSTER_CHANNEL_ID, REPO. Slack MCP (mcp__claude_ai_Slack__*) available.

  • Output: if a @claude dispatch is pending, claim and execute it;

otherwise return idle. start is emitted only AFTER pre-flight passes (NOT on session boot — pre-flight uses tool calls itself, so emitting start first would create a circular requirement). After the shared helpers are sourced, end is emitted on session close via trap, with reason set in the EXIT_REASON shell variable (e.g., declined, idle, error, task-done).

  • Side effects: posts comments on GitHub issues, opens branches/PRs,

posts presence events in ``.

  • Scope: handle @claude / @claude # for issue

implementation only. Ad-hoc @claude messages and @claude review # (review dispatch) are explicitly OUT of scope for this adapter — the implementation workflow below assumes a GitHub issue, not a PR.

Preconditions

  1. AGENT_ID is set AND starts with claude-:

```bash if [ -z "${AGENTID:-}" ]; then echo "Set AGENTID, e.g. claude-${HOSTNAME}-wsl-1" >&2 exit 1 fi

case "$AGENTID" in claude-) ;; ) echo "AGENTID '$AGENTID' is not a claude-* identity; this adapter is Claude-only" >&2 echo "Use the matching adapter for your model or fix AGENTID." >&2 exit 1 ;; esac ```

AGENT_ID is otherwise treated as an opaque registry key beyond the model prefix.

  1. GH_TOKEN_CLAUDE_BOT is set and works:

```bash if [ -z "${GHTOKENCLAUDEBOT:-}" ]; then echo "Set GHTOKENCLAUDEBOT before running the Claude adapter." >&2 exit 1 fi

GHTOKEN="$GHTOKENCLAUDEBOT" gh auth status ```

  1. Slack MCP is reachable. The required channel is ``

(channel ID in ROSTER_CHANNEL_ID env). The pinned roster message TS is in ROSTER_MESSAGE_TS.

  1. The shared protocol skill and helpers script are present and sourceable.

Helpers expect GH_TOKEN_BOT (model-agnostic name) and REPO; the Claude adapter resolves GH_TOKEN_BOT from GH_TOKEN_CLAUDE_BOT and exports both before sourcing (a one-shot prefix assignment like GH_TOKEN_BOT=... source ... would only set the var for the source builtin itself, not for later function calls):

```bash if [ ! -f .agent/skills/agent-presence/SKILL.md ] || [ ! -f scripts/agent-presence-helpers.sh ]; then echo "Missing shared agent-presence dependencies (.agent/skills/agent-presence/SKILL.md or scripts/agent-presence-helpers.sh)" >&2 exit 1 fi

export GHTOKENBOT="$GHTOKENCLAUDE_BOT" export REPO="/" source scripts/agent-presence-helpers.sh ```

Workflow

0. Wire shutdown wrapper

After preconditions pass and scripts/agent-presence-helpers.sh is sourced, wire the trap-based shutdown wrapper before pre-flight or dispatch work so an end event is emitted on every clean runtime exit path. Use an EXIT_REASON shell variable the workflow updates throughout, so the trap message is meaningful:

EXIT_REASON="boot-incomplete"  # default — overwritten as workflow progresses

emit_presence_end_once() {
  local reason="${EXIT_REASON}"
  trap - EXIT INT TERM HUP
  emit_presence end "${reason}" || true
}

trap 'emit_presence_end_once' EXIT
trap 'EXIT_REASON="interrupted (${EXIT_REASON})"; emit_presence_end_once; exit 130' INT
trap 'EXIT_REASON="terminated (${EXIT_REASON})"; emit_presence_end_once; exit 143' TERM
trap 'EXIT_REASON="hangup (${EXIT_REASON})"; emit_presence_end_once; exit 129' HUP

(MUST NOT use exec for any subsequent command — that would replace the shell and prevent the trap from firing.)

1. Pre-flight check (mandatory)

if ! pre_flight_check; then
    EXIT_REASON="declined (pre-flight conflict)"
    echo "Pre-flight detected conflicts; aborting before claiming." >&2
    # Pre-flight already wrote details to stderr. Operator decides next move.
    # Note: NO `start` event was emitted — pre-flight uses tool calls itself,
    # so emitting `start` first would create a circular requirement. The trap
    # below emits `end (declined ...)` so the operator still sees a lifecycle
    # event.
    exit 0  # NOT 1 — this is intentional refusal, not an error
fi

Pre-flight covers:

  1. Local git state check (no uncommitted changes from peer agents)
  2. Read pinned roster (cache layer)
  3. Cross-ref GitHub SSOT (in-flight issues, open PRs, claim comments with lease_until > now)
  4. Reconcile + flag conflicts (legacy Phase 1 markers also detected via synthetic lease = created_at + 4h)
  5. Worktree ownership advisory (if uncommitted changes exist that look like another agent's work, warn — operator should git worktree add for parallel work)

2. Emit start presence event

start is emitted AFTER pre-flight passes (not on session boot). Top-level message in dispatch channel:

emit_presence start
EXIT_REASON="post-start"  # workflow proceeded past start

3. Read dispatch channel for @claude mentions

Use Claude's Slack MCP (NOT the shell helpers' read functions) so the session has direct access to message content:

Tool: mcp__claude_ai_Slack__slack_read_channel
  channel_id: ${ROSTER_CHANNEL_ID}
  limit: 50

Filter messages matching the POSIX ERE pattern (^|[[:space:]])@claude[[:space:],:]+#?([0-9]+)([[:space:]]|$) (the leading (^|[[:space:]]) anchor stops substring matches like some-agent@claude5; the separator class catches @claude:, @claude,, plus whitespace; the trailing boundary stops the digit run from absorbing unrelated trailing text). The issue number is capture group 2. Skip messages older than 1h to avoid replaying stale dispatches. Scope is implementation only — @claude review # patterns are NOT actionable in this adapter and should be skipped silently.

4. For each actionable dispatch

Call the shared claim algorithm via helpers:

ISSUE_NUM=

# Default task type is 'quick' (30 min lease). For known long-running
# patterns (merge supervisor, content batch), declare 'standard' or 'long'
# upfront based on the issue body's "Acceptance Criteria" or "Estimate"
# section.
TASK_TYPE=quick  # or standard|long based on issue inspection

if claim_issue "$ISSUE_NUM" "$TASK_TYPE"; then
    # Won the race — proceed to execute
    # claim_issue already emitted `busy` and updated roster
    :
else
    # Lost the race or eventual-consistency failed — claim_issue handled cleanup
    continue  # try next dispatch in the loop
fi

5. Execute the task

Use the standard Claude Code task workflow per CLAUDE.md:

  1. Read the issue body via gh issue view
  2. Branch: git checkout -b fix/issue-- or feature/issue--
  3. Implement the change using Claude's Edit/Write tools
  4. Verify locally per the project's Verification After Changes checklist

(typecheck, lint, build, tests as applicable)

  1. Commit on the feature branch with a descriptive message
  2. Open PR with body Closes #${ISSUE_NUM} and capture the number:

``bash PR_URL=$(gh pr create --fill --body "Closes #${ISSUE_NUM}") PR_NUM=$(gh pr view "$PR_URL" --json number --jq '.number') ``

6. Renew lease during long execution

If the task is taking longer than TTL/2 (15 min for quick, 1 h for standard, 2 h for long), renew the lease BEFORE expiration:

renew_lease "$ISSUE_NUM"  # uses original TTL by default

Skip renewal once the PR is opened — the PR's commit activity itself proves liveness, and the takeover gate's "no GH activity" condition will fail safely for any peer agent.

7. Report completion

After the PR is opened (or task is blocked), update EXIT_REASON so the trap-fired end event later carries useful context:

# Success path
emit_presence ready "PR #${PR_NUM} done"
update_roster_chat_update --event ready --clear-task
EXIT_REASON="task-done (PR #${PR_NUM})"

# Block path
emit_presence ready "blocked #${ISSUE_NUM}: "
update_roster_chat_update --event ready --stale-reason "blocked #${ISSUE_NUM}"
# Also leave a :warning: thread reply on the original dispatch message
# (use mcp__claude_ai_Slack__slack_send_message with thread_ts)

emit_presence only posts the Slack presence message — it does not touch the pinned roster. Call update_roster_chat_update explicitly (as above) so the roster entry reflects the new state and the operator sees the agent is back to idle.

8. Idle exit

When the dispatch loop has no more actionable items, set the exit reason and exit cleanly. The trap fires the end event with the right context:

EXIT_REASON="idle (no actionable dispatches)"
exit 0

Slack MCP wiring (Claude-specific)

For CHANNEL READ + THREAD POST use Claude's Slack MCP tools directly:

| Action | MCP tool | |---|---| | Read 50 dispatch messages | mcp__claude_ai_Slack__slack_read_channel | | Read a thread for context | mcp__claude_ai_Slack__slack_read_thread | | Post a thread reply (:eyes:, :warning:) | mcp__claude_ai_Slack__slack_send_message with thread_ts | | Post a top-level message | helpers' emit_presence (uses curl + xoxb token) | | Update pinned roster | helpers' update_roster_chat_update (uses curl + chat.update) |

Why split between MCP and curl? The MCP tools work great for content reads/replies but are not available in the helpers shell library. The helpers use raw Slack Web API via curl for atomic operations (emit_presence, chat.update) so any CLI can call them. Claude can use either path for top-level posts; preferring helpers keeps the codebase DRY.

Required env summary

# Identity (per-machine)
export AGENT_ID="claude-${HOSTNAME}-wsl-1"  # or your environment slug

# Bot tokens (per-machine .envrc / .env.local — never committed)
export GH_TOKEN_CLAUDE_BOT="ghp_..."
export SLACK_BOT_TOKEN="xoxb-..."

# Repository (matches .agent/context/project-ids.md)
export REPO="/"

# Roster pinned message (set up once per project; see project-ids.md)
export ROSTER_MESSAGE_TS=""
export ROSTER_CHANNEL_ID=""

# Helpers expect a model-agnostic name
export GH_TOKEN_BOT="$GH_TOKEN_CLAUDE_BOT"

# Source helpers (REPO must already be exported)
if [ ! -f .agent/skills/agent-presence/SKILL.md ] || [ ! -f scripts/agent-presence-helpers.sh ]; then
  echo "Missing shared agent-presence dependencies (.agent/skills/agent-presence/SKILL.md or scripts/agent-presence-helpers.sh)" >&2
  exit 1
fi
source scripts/agent-presence-helpers.sh

Out of scope

  • Auto-dispatch by PM — separate spec
  • Polling loop after first task done — Claude session exits after one

dispatch cycle for now; the operator restarts when needed

  • Cost optimization (idle exit at 30 min, polling backoff)
  • Cross-project dispatch (@claude other-repo#123)
  • Slack DM dispatch — channel only

Shared protocol references

  • .agent/skills/agent-presence/SKILL.md — shared protocol contract
  • scripts/agent-presence-helpers.sh — shared shell library
  • .agent/skills/dispatch-watcher/SKILL.md — Codex adapter (parallel to

this one, same shape)

  • .agent/skills/dispatch-watcher-gemini/SKILL.md — Gemini adapter

Stop conditions (per shared agent-presence skill)

Stop and ask the human or PM agent when:

  • Two active agents are editing the same non-generated file set
  • A lease is expired but there is fresh PR/branch/commit activity
  • The task requires overwriting uncommitted changes you did not make
  • The roster and GitHub disagree in a way that changes ownership
  • Secrets, tokens, or private Slack/GitHub identities would need to be exposed

If pre-flight detects any of these, exit cleanly (trap emits end) and let the operator decide.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.