emreozyoruk/hush

Issue triage that stays quiet when it isn't sure. Calibrated labels, spam and duplicate detection — with abstention.

JavaScript

0

3 commits

updated Sep 20, 2026

See the code
automation
decision-model
github-action
github-actions
issue-triage
jev
labels
maintainer-tools
triage
typesafe

See what people are saying (1)

README

hush

Issue triage that stays quiet when it isn't sure.

Marketplace Tests License: MIT

A GitHub Action that reads every new issue and labels it — but only when it can say how sure it is. When it can't, it does nothing and tells you why.

hush answering four questions about an issue and acting on the two it was sure of

■ "Crash on save when filename is very long"
  bug            100% ≥ 80%, confidence 100%     → applied `bug`
  duplicate       97% ≥ 85%                      → applied `possible-duplicate`
  spam             3% < 90%                      → stayed quiet
  needs info      14% < 85%                      → stayed quiet

■ "🔥 BUY CHEAP FOLLOWERS 🔥"
  spam            99% ≥ 90%                      → applied `spam`
  label          best fit was "none" (100%)      → stayed quiet

Real output, 202–530 ms per issue.

And the part that matters, from its own first issue — a report that could reasonably be a bug or a documentation problem:

label | stayed quiet | bug 72% / confidence 63% — below 80% / 60%

It had an answer. It wasn't sure enough. So it said nothing.

Why another triage bot

Because the others talk when they shouldn't.

LLM triage bots return prose with no calibrated notion of certainty, so a 55% hunch and a 99% read look identical coming out. You get confident-sounding labels on issues the model never understood, and after the third wrong one you turn it off.

hush runs on Jev, a decision model that returns a probability instead of a sentence. Every verdict comes with the number that produced it, and every threshold is yours:

label-threshold: 0.80        # how likely the label must be
label-confidence-threshold: 0.60  # and how much the model must trust its own read
spam-threshold: 0.90
needs-info-threshold: 0.85
duplicate-threshold: 0.85

Below the line, it abstains. Silence is the default behaviour, not the failure mode.

It is also cheap enough to leave on: one request per issue, around $0.00002. Ten thousand issues cost about twenty cents.

Install

# .github/workflows/triage.yml
name: triage
on:
  issues:
    types: [opened, edited, reopened]

permissions:
  issues: write

jobs:
  hush:
    runs-on: ubuntu-latest
    steps:
      - uses: emreozyoruk/hush@v1
        with:
          typesafe-api-key: ${{ secrets.TYPESAFE_API_KEY }}
          apply: false   # watch it first

Get a key at console.typesafe.ai/keys and add it as a repository secret.

Start with apply: false. hush writes a table into the job summary of every run showing what it would have done. Read a week of those, move your thresholds, then set apply: true.

Your labels, your words

The default set is bug, feature, docs, question. Replace it with your own — the description is what the model judges against, so write it the way you would explain the label to a new maintainer:

- uses: emreozyoruk/hush@v1
  with:
    typesafe-api-key: ${{ secrets.TYPESAFE_API_KEY }}
    apply: true
    comment: true
    labels: |
      {
        "bug": "A defect: the library does something other than what the docs say.",
        "performance": "It works, but it is too slow or uses too much memory.",
        "platform/windows": "Specific to Windows; does not reproduce on Linux or macOS.",
        "good first issue": "Small, well-understood, and does not need project context."
      }

A label is only ever applied if it already exists in the repository. hush never creates labels, never removes one, and never touches a label a human added.

What it decides

questiontypewhat it does
labelone of yours, or noneApplies the category when both the probability and the model's confidence clear your thresholds.
spamyes/noApplies spam.
needs more infoyes/noApplies needs-more-info when a maintainer would have to ask before acting.
duplicateyes/noCompares against the 40 most recent open issues and applies possible-duplicate.

All four travel in one request, which is why triage costs a fraction of a cent and finishes before the page reloads.

Inputs

inputdefault
typesafe-api-keyRequired.
github-token${{ github.token }}Needs issues: write to apply anything.
applyfalseAct, rather than only report.
commentfalseExplain the labels in a comment.
labelsthe four aboveJSON object of name → meaning.
check-duplicatestrueCompare against other open issues.
label-threshold0.80
label-confidence-threshold0.60
spam-threshold0.90
needs-info-threshold0.85
duplicate-threshold0.85

What it will not do

  • Close, lock, delete or edit anything. It labels, and optionally comments.
  • Apply a label that does not already exist in your repository.
  • Remove or overwrite a label a person set.
  • Act at all while apply is false.

Where it is unsure, it leaves the issue exactly as it found it. That is the entire idea.

Development

npm test     # the decision layer is pure; the thresholds are tested offline

No build step and no dependencies — the action runs the files in src/ directly on Node 20.

Licence

MIT

Contributors

emreozyoruk

3 commits

emreozyoruk/hush

Issue triage that stays quiet when it isn't sure. Calibrated labels, spam and duplicate detection — with abstention.

JavaScript

0

3 commits

updated Sep 20, 2026

See the code
automation
decision-model
github-action
github-actions
issue-triage
jev
labels
maintainer-tools
triage
typesafe

See what people are saying (1)

README

hush

Issue triage that stays quiet when it isn't sure.

Marketplace Tests License: MIT

A GitHub Action that reads every new issue and labels it — but only when it can say how sure it is. When it can't, it does nothing and tells you why.

hush answering four questions about an issue and acting on the two it was sure of

■ "Crash on save when filename is very long"
  bug            100% ≥ 80%, confidence 100%     → applied `bug`
  duplicate       97% ≥ 85%                      → applied `possible-duplicate`
  spam             3% < 90%                      → stayed quiet
  needs info      14% < 85%                      → stayed quiet

■ "🔥 BUY CHEAP FOLLOWERS 🔥"
  spam            99% ≥ 90%                      → applied `spam`
  label          best fit was "none" (100%)      → stayed quiet

Real output, 202–530 ms per issue.

And the part that matters, from its own first issue — a report that could reasonably be a bug or a documentation problem:

label | stayed quiet | bug 72% / confidence 63% — below 80% / 60%

It had an answer. It wasn't sure enough. So it said nothing.

Why another triage bot

Because the others talk when they shouldn't.

LLM triage bots return prose with no calibrated notion of certainty, so a 55% hunch and a 99% read look identical coming out. You get confident-sounding labels on issues the model never understood, and after the third wrong one you turn it off.

hush runs on Jev, a decision model that returns a probability instead of a sentence. Every verdict comes with the number that produced it, and every threshold is yours:

label-threshold: 0.80        # how likely the label must be
label-confidence-threshold: 0.60  # and how much the model must trust its own read
spam-threshold: 0.90
needs-info-threshold: 0.85
duplicate-threshold: 0.85

Below the line, it abstains. Silence is the default behaviour, not the failure mode.

It is also cheap enough to leave on: one request per issue, around $0.00002. Ten thousand issues cost about twenty cents.

Install

# .github/workflows/triage.yml
name: triage
on:
  issues:
    types: [opened, edited, reopened]

permissions:
  issues: write

jobs:
  hush:
    runs-on: ubuntu-latest
    steps:
      - uses: emreozyoruk/hush@v1
        with:
          typesafe-api-key: ${{ secrets.TYPESAFE_API_KEY }}
          apply: false   # watch it first

Get a key at console.typesafe.ai/keys and add it as a repository secret.

Start with apply: false. hush writes a table into the job summary of every run showing what it would have done. Read a week of those, move your thresholds, then set apply: true.

Your labels, your words

The default set is bug, feature, docs, question. Replace it with your own — the description is what the model judges against, so write it the way you would explain the label to a new maintainer:

- uses: emreozyoruk/hush@v1
  with:
    typesafe-api-key: ${{ secrets.TYPESAFE_API_KEY }}
    apply: true
    comment: true
    labels: |
      {
        "bug": "A defect: the library does something other than what the docs say.",
        "performance": "It works, but it is too slow or uses too much memory.",
        "platform/windows": "Specific to Windows; does not reproduce on Linux or macOS.",
        "good first issue": "Small, well-understood, and does not need project context."
      }

A label is only ever applied if it already exists in the repository. hush never creates labels, never removes one, and never touches a label a human added.

What it decides

questiontypewhat it does
labelone of yours, or noneApplies the category when both the probability and the model's confidence clear your thresholds.
spamyes/noApplies spam.
needs more infoyes/noApplies needs-more-info when a maintainer would have to ask before acting.
duplicateyes/noCompares against the 40 most recent open issues and applies possible-duplicate.

All four travel in one request, which is why triage costs a fraction of a cent and finishes before the page reloads.

Inputs

inputdefault
typesafe-api-keyRequired.
github-token${{ github.token }}Needs issues: write to apply anything.
applyfalseAct, rather than only report.
commentfalseExplain the labels in a comment.
labelsthe four aboveJSON object of name → meaning.
check-duplicatestrueCompare against other open issues.
label-threshold0.80
label-confidence-threshold0.60
spam-threshold0.90
needs-info-threshold0.85
duplicate-threshold0.85

What it will not do

  • Close, lock, delete or edit anything. It labels, and optionally comments.
  • Apply a label that does not already exist in your repository.
  • Remove or overwrite a label a person set.
  • Act at all while apply is false.

Where it is unsure, it leaves the issue exactly as it found it. That is the entire idea.

Development

npm test     # the decision layer is pure; the thresholds are tested offline

No build step and no dependencies — the action runs the files in src/ directly on Node 20.

Licence

MIT

Contributors

emreozyoruk

3 commits

Languages

JavaScript

100.0%