Skip to content
Back to skills

Setup

BSecurity

Set up agent-dispatch on this machine: check what a tick needs, ask each repo's auto-merge threshold, check the optional notification hook, then show one summary of the repo list, labels, tick copy and scheduler entry. Apply it once the owner confirms. Use when the user wants GitHub labels to start issue-to-pr runs on their machine, or asks why the dispatcher is not picking issues up.

  • 8 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 29, 2026
ai-agentsgoshellbashexpressgit

Works with

  • cli

Security analysis

B75/100
  • criticalModifies startup scripts or system services for persistence

Pro shows the line behind each finding and how to fix it

Scanned October 2, 2026

npx -y skills add DmitriyYukhanov/claude-plugins --skill setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Setup
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/dmitriyyukhanov-setup-claude-plugins/badge)](https://www.skillsdirectory.com/skills/dmitriyyukhanov-setup-claude-plugins)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: setup
description: >-
  Set up agent-dispatch on this machine: check what a tick needs, ask each repo's auto-merge
  threshold, check the optional notification hook, then show one summary of the repo list, labels,
  tick copy and scheduler entry. Apply it once the owner confirms. Use when the user wants GitHub labels to start
  issue-to-pr runs on their machine, or asks why the dispatcher is not picking issues up.
---

# setup: label an issue, get a run

**Change nothing until the owner confirms the one summary in step 5; then apply all of it.** The
checks and questions before it only read. Resolve `S/` to `../../scripts/` relative to this
`SKILL.md`, as an absolute path.

## 1. Checks

Any blocker below stops setup here: show its fix, say nothing changed, and have the owner run
setup again once it is fixed.

1. Bash, git and `gh`: run the `issue-to-pr:setup` checks (select that skill). Its result stands;
   do not repeat it here.
2. `~/.agent-dispatch/repos.conf`, one repo per line:

   ```
   # <main checkout, absolute path> | claude|codex | trivial|standard|complex|none
   /home/you/code/my-app | claude | trivial
   C:\Users\you\code\my-app | codex | standard
   ```

   Missing is fine: step 5 creates it. For each existing line: the path is a git checkout
   (`git -C <path> rev-parse --show-toplevel` prints that path; `C:\x` and `C:/x` are the same
   one, so compare them as directories, not strings) and `gh repo view --json nameWithOwner` in
   it names a GitHub repo. A line that fails is a blocker: one bad line stops
   every tick, for every repo. Setup never edits an existing line.
3. `agent-dispatch` itself, on the host you are running in now: `tick.sh` refuses to run at all
   when it cannot tell which install is active, so exactly one has to be true here. In Claude
   Code, `~/.claude/plugins/installed_plugins.json` must register `agent-dispatch` in exactly
   one scope (project or user), not both. In Codex, exactly one version may be cached under
   `~/.codex/plugins/cache/*/agent-dispatch/`, and its `[plugins."agent-dispatch@…"]` section in
   `~/.codex/config.toml` must say `enabled = true`. Two hits, or a disabled plugin, in either
   place is a blocker: tell the owner to uninstall the extra copy, or enable the plugin, so one
   install is active per host.
4. Windows only: `command -v pwsh`. A path under `WindowsApps` is the Store build: a Store `pwsh`
   started inside a run survives the job object that lets a tick stop the run, and Codex runs its
   commands through `pwsh`. Not a blocker: unless the machine PATH
   (`[Environment]::GetEnvironmentVariable('Path','Machine')`) already lists
   `PowerShell\7`, step 5 offers the machine-wide MSI build:
   `winget install --id Microsoft.PowerShell -e --source winget --installer-type wix --scope machine
   --accept-source-agreements --accept-package-agreements` (the last two answer the prompts a
   first `winget` run shows, which nobody can answer here).
   The Store package can stay: the machine PATH comes before the user PATH that holds
   `WindowsApps`, so once it lists the MSI build, that build wins. Also resolve
   `bash.exe`: run `git --exec-path`, which prints `<git root>/mingw64/libexec/git-core` from the
   real install even when `git` on PATH is a scoop shim; drop the last three path parts and add
   `bin/bash.exe`. That is what fills `<bash.exe path>` below. Do not guess
   `$env:ProgramFiles\Git\bin\bash.exe`; that path only holds for a machine-wide install.

5. `~/.agent-dispatch/notify.sh`. Missing is not a blocker: say that runs cannot notify the owner
   until the hook exists, and show the example below. If present, keep it and show only its test
   command. Do not run the hook unless the owner expressly asks for a test: it sends a real message.

## 2. Repos

Ask which repos a label should dispatch: their main checkouts, as absolute paths. Offer the
current repo's main checkout (the first `worktree` line of `git worktree list --porcelain`, not a
linked worktree, which a finished run deletes) if it names a GitHub repo. Check each path as in
check 2; a repo that `repos.conf` already lists (same `nameWithOwner`) keeps its line and is not
asked about again. The host for a new line is the one you are running in now.

## 3. Auto-merge threshold

One plain question per new repo, with no default: the third field of its `repos.conf` line, the
`--auto-merge` value every run in that repo gets. A repo with no answer gets no line, so nothing is
dispatched for it. Say what the answer means:

- Each run sorts itself into a tier: `trivial` is a short copy or config change on one path,
  `complex` is new behavior, several checklist items or paths, or a `design`, `ux` or `breaking`
  label, and `standard` is everything else.
- A PR merges without the owner only when all of these hold: its tier is at or under the
  threshold (`trivial` < `standard` < `complex`; `none` never merges alone), the run asked the
  owner nothing, the issue never stopped on an earlier run, the reviews came back clean, and the
  diff touches nothing under `human_paths` in the repo's `.claude/issue-to-pr/config.md`.
  Everything else waits at `agent:review` for the owner's `merge` reply.
- A self-merge lands on the base branch like any merge. Where a push there publishes a release or
  deploys, a self-merge is a release; a headless run also runs the config's `after_merge`, usually
  a deploy, after it merges.

Read that checkout's `.claude/issue-to-pr/config.md` and name what its `human_paths` and
`after_merge` say for this repo, or that it sets neither.

## 4. Hosts

For every host the finished list names (existing lines and new ones):

- `claude`: `claude -p "Reply with ok." < /dev/null` prints ok (logged in); `claude plugin list`
  shows issue-to-pr at 9.4.0 or newer.
- `codex`: `codex login status` says logged in; the only directory under
  `~/.codex/plugins/cache/*/issue-to-pr/` is 9.4.0 or newer.

A logged-out host or an older issue-to-pr is a blocker, as in step 1: show the fix (`/login`,
`codex login`, `/plugin update issue-to-pr`, `codex plugin marketplace upgrade`) and stop.

## 5. Confirm and apply

Show one summary of everything setup is about to change, in the order it will run, each item as
the exact command or file content with every `<...>` filled in (below), and each marked *new*,
*replaces* or *unchanged*:

1. Windows, Store `pwsh` only: the `winget` command from check 4. It shows a UAC prompt the owner
   has to accept.
2. The five labels for every repo in the list, existing lines included.
3. The lines to append to `repos.conf`, one per new repo, each with its threshold.
4. The `tick.sh` copy.
5. The scheduler entry for this OS. It is *unchanged*, and not run, only when it is both in place
   and live: on Windows `Get-ScheduledTask agent-dispatch` exists, its `State` is not `Disabled`,
   and its action runs this `$bash`, `$tick` and `<host>`; on macOS the plist holds exactly this
   content and `launchctl print gui/$(id -u)/agent-dispatch` finds the job; on Linux the unit
   files hold exactly this content and `systemctl --user is-enabled agent-dispatch.timer` and
   `is-active` both succeed.
   On macOS, replacing a loaded job while `~/.agent-dispatch/lock/` exists stops the run in
   progress: the bootout below ends the tick, and the next tick stops its run and marks the issue
   failed. Say so on this item.

Then ask once: **Apply all of this?** A reply that corrects an item (another host, which the Hosts
step then checks, another threshold, or leaving out the `winget` install) updates the summary and
asks again. `no` changes nothing. `yes` runs the items in that order. The labels come before
`repos.conf` because a listed repo is dispatched on the next tick, and a run cannot label an issue
with a label the repo lacks. The scheduler goes last because it fires a tick at once, and a tick
with no `repos.conf` dies. Stop at the first failure and roll nothing back: every item is safe to
run twice, so running setup again is the recovery.

Give `winget` the longest timeout the host allows: it waits on the owner's UAC click. Judge it by
the machine PATH, not its exit code (a reboot request or an already installed package exits
nonzero too): read the PATH again with the `[Environment]` call from check 4; it must list
`PowerShell\7`. This shell's own PATH was read before the install and proves nothing.

**`repos.conf`**: create `~/.agent-dispatch/` if missing and append the new lines, adding a newline
first when the file does not end with one; a line glued onto the one before it is a malformed line.

**Labels**, once per repo (`-f` updates a label that already exists):

```
gh label create agent -R <owner/repo> -f -c 1d76db -d "Queued for an agent run"
gh label create agent:running -R <owner/repo> -f -c fbca04 -d "An agent run is working on it"
gh label create agent:waiting -R <owner/repo> -f -c d93f0b -d "The agent asked a question"
gh label create agent:review -R <owner/repo> -f -c 0e8a16 -d "A PR waits for your merge"
gh label create agent:failed -R <owner/repo> -f -c b60205 -d "The run failed; see the last comment"
```

**Notifications.** Every comment and label a run makes is the owner's own, and GitHub does not
notify you of your own activity. So when an issue the tick ran stops at `agent:waiting`,
`agent:review` or `agent:failed`, the tick runs `~/.agent-dispatch/notify.sh` if it exists:
`bash notify.sh <waiting|review|failed> <owner/repo> <issue> <detail>`. `<detail>` is the cause
when the dispatcher itself marked the run failed (plus a sentence when dispatching paused), empty
otherwise. The hook gets 30 seconds, then it is stopped with everything it started; its output
goes to `~/.agent-dispatch/logs/notify.log`; it runs under the scheduler's PATH, not a login
shell. The plugin ships no channel. Show this example for the ntfy phone app (subscribe to the
same topic there; anyone who knows the topic can read it, so make it long and random, and on a
private repo remember the repo names go to that server):

```bash
# ~/.agent-dispatch/notify.sh
case "$1" in
  waiting) what='has a question for you' ;;
  review) what='has a PR waiting for your merge' ;;
  *) what='failed' ;;
esac
curl -fsS -m 10 -H "Title: $2#$3 $what" -H "Click: https://github.com/$2/issues/$3" \
  -d "${4:-Open the issue for details.}" "https://ntfy.sh/<your-topic>" >/dev/null
```

Any command works in its place (a Telegram bot, a desktop notification, mail); keep a time limit
on network calls. The owner tests it with `bash ~/.agent-dispatch/notify.sh review <owner/repo> 1 ''`.

**The tick.** Copy the shim; it finds the current install on every call:

```
mkdir -p ~/.agent-dispatch && cp "S/tick.sh" ~/.agent-dispatch/tick.sh
```

`<host>` below is the host you are running in now (`claude` or `codex`): the tick reads that
host's install record.

**Scheduler**, for the current OS only. Every `<...>` placeholder below (`<host>`, `<home>`,
`<bash.exe path>`, the PATH list) stands for a real value on this machine; fill each one in before
you show it. launchd, systemd and the Windows script all read a literal `<...>` as text, not
something they resolve for you, and a literal `<...>` left in the plist is invalid XML that
`launchctl bootstrap` will refuse.

- Windows, in PowerShell (Git for Windows' `bash.exe`, resolved in check 4 above; a bare `bash` is
  the WSL launcher). Wrapping bash in `conhost.exe --headless` is what keeps the scheduled run
  from flashing a console window open every three minutes; `-Force` replaces the task a
  previous setup registered:

  ```powershell
  $bash = "<bash.exe path>"
  $tick = "$HOME\.agent-dispatch\tick.sh"
  $a = New-ScheduledTaskAction -Execute 'conhost.exe' -Argument "--headless `"$bash`" `"$tick`" <host>"
  $t = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 3)
  $s = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit ([TimeSpan]::Zero) `
    -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
  Register-ScheduledTask -TaskName agent-dispatch -Action $a -Trigger $t -Settings $s -Force
  ```

  It runs only while you are logged on, which is what lets it use your CLI logins. Run a tick now
  with `Start-ScheduledTask agent-dispatch`.
- macOS: `~/Library/LaunchAgents/agent-dispatch.plist` (a LaunchAgent runs in your login
  session, so the Keychain holding the logins is readable). `<home>` below is `$HOME`'s value on
  this machine, written out in full: launchd never expands `~`, so the plist needs the absolute
  path already:

  ```xml
  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0"><dict>
    <key>Label</key><string>agent-dispatch</string>
    <key>ProgramArguments</key><array>
      <string>/bin/bash</string><string><home>/.agent-dispatch/tick.sh</string><string><host></string>
    </array>
    <key>EnvironmentVariables</key><dict>
      <key>PATH</key><string><the directories of gh, claude and codex>:/usr/bin:/bin</string>
    </dict>
    <key>StartInterval</key><integer>180</integer>
  </dict></plist>
  ```

  Load it with `launchctl bootout gui/$(id -u)/agent-dispatch 2>/dev/null; launchctl bootstrap
  gui/$(id -u) ~/Library/LaunchAgents/agent-dispatch.plist`. The bootout unloads what a previous
  setup loaded, since bootstrap refuses a loaded job; its error when nothing was loaded is
  expected. A bootstrap that fails with `5: Input/output error` ran before the unload finished:
  wait a second and retry it once. Run a tick now with `launchctl kickstart
  gui/$(id -u)/agent-dispatch`. launchd never starts a second copy while one runs.
- Linux: two files under `~/.config/systemd/user/`:

  ```ini
  # agent-dispatch.service
  [Service]
  Type=oneshot
  TimeoutStartSec=infinity
  Environment=PATH=<the directories of gh, claude and codex>:/usr/bin:/bin
  ExecStart=/bin/bash %h/.agent-dispatch/tick.sh <host>

  # agent-dispatch.timer
  [Timer]
  OnBootSec=2min
  OnUnitActiveSec=3min
  [Install]
  WantedBy=timers.target
  ```

  Then `systemctl --user daemon-reload && systemctl --user enable --now agent-dispatch.timer`;
  run a tick now with `systemctl --user start agent-dispatch`. A oneshot unit never runs twice at
  once.

Find the PATH list with `command -v gh`, `command -v claude` and `command -v codex` on this
machine. Run a tick by hand only through the scheduler, as above: it is what keeps two ticks from
overlapping.

## 6. Report

One block: the checks, then each summary item as applied, failed (with its error) or not reached;
after `no`, one line saying nothing changed. Say whether the notification hook exists; if it is
missing, explain that the owner will receive no notifications until they configure it. Then one
line per repo, `<owner/repo> | <host> | <threshold>`, and the saved search to bookmark on GitHub web or mobile, since GitHub does not
notify you of your own comments and every comment here is yours:
`is:issue label:agent:waiting,agent:review,agent:failed repo:<owner/repo>` (one `repo:` per line
of `repos.conf`). Close with how it behaves: label an issue `agent`;
the tick log is `~/.agent-dispatch/logs/tick.log`, each run's log sits next to it; a CLI error
creates `~/.agent-dispatch/paused`, and dispatching resumes once the owner deletes that file.
Disabling or uninstalling the plugin does not stop the scheduled tick: to stop dispatching, delete
the scheduler entry or create `~/.agent-dispatch/paused`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.