GitHub Action
The quickest way to try Dispatch on a repository you already control.
Setup
bash
npx @dispatch-triage/cli initThat writes .github/dispatch.yml, a workflow, and a directory for template overrides. Then add your TypeSafe key as a repository secret named TYPESAFE_API_KEY.
The workflow
yaml
name: Dispatch
on:
issues:
types: [opened, edited, reopened]
pull_request:
types: [opened, edited, ready_for_review, synchronize]
issue_comment:
types: [created]
permissions:
contents: read
issues: write
pull-requests: write
concurrency:
group: dispatch-${{ github.event.issue.number || github.event.pull_request.number }}
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: JeelGajera/Dispatch/apps/action@v0.1.0
with:
jev-key: ${{ secrets.TYPESAFE_API_KEY }}
- if: always()
uses: actions/upload-artifact@v4
with:
name: dispatch-decisions-${{ github.run_id }}
path: .dispatch/decisions.jsonl
if-no-files-found: ignoreThe concurrency group matters: GitHub's secondary rate limits punish bursts of content creation, and two runs racing on one item can produce two comments.
Reading the decision log
In shadow mode the artifact is the whole point. Download it, read what Dispatch would have done, and disagree with it in .github/dispatch.yml until you stop disagreeing.
Pull requests from forks
Read this before using pull_request_target
pull_request from a fork gets a read-only token and no secrets. That is correct and safe, and it means Dispatch cannot label the pull request or read your API key.
pull_request_target gets a write token and your secrets, and runs the workflow from the base branch. It is safe only if you never check out or execute the pull request's code. An attacker opening a pull request controls that code completely.
So the sample workflow has no checkout step, and must never gain one. Dispatch reads pull request metadata through the API and executes nothing from the head.
If you need to build or test fork code, do it in a separate pull_request workflow with no secrets.
See examples/workflows/fork-pull-requests.yml.
Escalations
Some rules depend on time passing — "this has been waiting on a reproduction for fourteen days" is not something GitHub notifies about. Run those on a schedule:
yaml
on:
schedule:
- cron: '30 3 * * *'
jobs:
escalate:
runs-on: ubuntu-latest
steps:
- uses: JeelGajera/Dispatch/apps/action@v0.1.0
with:
jev-key: ${{ secrets.TYPESAFE_API_KEY }}
command: escalateInputs
| Input | Default | Notes |
|---|---|---|
jev-key | — | Required. Pass a secret, never a literal. |
config-path | .github/dispatch.yml | |
mode | from config | Overrides the configured mode. |
command | triage | triage, escalate, or backfill. |
dry-run | false | Decide and log, whatever the mode says. |
upload-log | true | Write the decision log for the artifact step. |
Duplicate detection
Runs automatically on newly opened issues when dedup.enabled is true. It costs one extra provider request, and none at all when the index turns up nothing plausible.
yaml
dedup:
enabled: true
candidates: 30
reportThreshold: 0.75
includeClosed: trueIt applies possible-duplicate and posts the ranked candidates with their probabilities. It never closes anything, at any confidence, in any mode.
The index is built as Dispatch sees items, so it is empty on day one and gets better as it runs.
Escalations
command: escalate applies the escalations: block. It is driven by the decision log rather than a repository scan, so it only ever follows up on items Dispatch itself labelled — and it stops the moment a maintainer removes that label, because that removal was their decision.
/dispatch explain
Posts the full question, answer and confidence table into the bot comment, including what was held back and why. This is the command that makes a decision arguable rather than mysterious.