Skip to content

Rules cookbook

Patterns that work, and the reasoning behind each threshold. Copy them, then tune the numbers against your own dispatch eval output rather than trusting mine.

Ask for a reproduction

yaml
- id: needs-repro
  when: [issue.opened, issue.edited]
  if: "answers.type == 'bug' && answers.has_repro < 0.3"
  then:
    - { op: label, add: [needs-repro] }
    - { op: comment, template: needs-repro.md, key: needs-repro }

< 0.3 rather than < 0.5: a false positive here tells someone who did include a reproduction that they did not, which is annoying enough to lose a contributor.

Area labels from your taxonomy

yaml
labels:
  area:
    parser: 'Markdown tokenization and AST construction'
    renderer: 'PDF drawing, layout, pagination, fonts'

rules:
  - id: area-label
    when: [issue.opened, pull_request.opened]
    if: "answers.label_area != '__none__'"
    then:
      - { op: label, add: ['area:{{answers.label_area}}'] }

Each dimension becomes one Choice question, and your descriptions are the rubric the model reads. Write them for someone who has never seen your repository — that wording does more for quality than any threshold.

Always handle __none__. The model is never forced into a wrong bucket, and a rule that ignores the escape hatch will label everything.

Flag risky changes for careful review

yaml
- id: careful-review
  when: [pull_request.opened, pull_request.synchronize]
  if: 'answers.risk >= 2.5 || answers.touches_security_surface > 0.7'
  gateOn: [risk, touches_security_surface]
  then:
    - { op: label, add: [needs-careful-review] }
    - { op: request_review, codeowners: true }

risk is a score, and scores are fractional. >= 2.5 means "at least halfway between 'crosses module boundaries' and 'touches security or release machinery'". Never round it.

gateOn is explicit here because the if uses ||: without it the gate would be the minimum across both questions even when only one of them fired the rule.

Combine signals rather than asking one big question

yaml
- id: needs-tests
  when: [pull_request.opened, pull_request.synchronize]
  if: 'answers.needs_tests > 0.7 && answers.risk >= 1.5'
  then:
    - { op: label, add: [needs-tests] }

"Untested" alone flags every typo fix. "Untested and non-trivial" is the thing you actually care about. This is the pattern the whole design is built for: decompose the judgement, weigh it in a rule, and tune a number instead of rewriting a prompt.

Close spam — carefully

yaml
allowDestructive: [close]

rules:
  - id: spam-gate
    when: issue.opened
    if: "answers.is_spam > 0.95 && author.association == 'FIRST_TIME_CONTRIBUTOR'"
    then:
      - { op: label, add: [spam] }
      - { op: close, reason: not_planned }
    confidence: { auto: 0.97, suggest: 0.9 }

Three separate guards, because closing a real report is the worst thing Dispatch can do:

  1. close must be named in allowDestructive, or it is suppressed no matter what
  2. 0.95 on the answer itself
  3. auto: 0.97 on the rule, well above the repository default

The association check matters too: a spam classifier that can close an issue from a long-time contributor is a classifier with too much authority.

Welcome a first contribution

yaml
- id: welcome
  when: [pull_request.opened]
  if: "author.association == 'FIRST_TIME_CONTRIBUTOR' && answers.low_effort < 0.3"
  then:
    - { op: comment, template: welcome.md, key: welcome }

The low_effort guard stops Dispatch thanking someone warmly for typo-farming.

Escalate stale items

yaml
escalations:
  staleNeedsRepro:
    afterDays: 14
    then:
      - { op: comment, template: stale.md, key: stale }
      - { op: close, reason: not_planned }

Needs the scheduled workflow — "nothing has happened for fourteen days" is not an event GitHub sends.

Things to avoid

Do not gate on a question that is not asked for that trigger. An issue-only question in a pull_request.opened rule means the rule can never fire. dispatch validate warns about exactly this.

Do not set auto below suggest. It makes no sense to apply an action you would not have been willing to propose, and it is a validation error.

Do not reach for close early. Labels are reversible by one click. A closed issue is a contributor deciding not to come back.

MIT licensed.