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

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.

■ "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.

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.85Below 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.

Before adding anything to a repository, point it at one:

export TYPESAFE_API_KEY=... # console.typesafe.ai/keys

npx github:emreozyoruk/hush owner/reporemotion-dev/remotion · 6 open issues · nothing will be written

#11474 Codemods: It always creates a full copy of the enti… needs-more-info, bug

· spam spam 4% < 90%

· duplicate duplicate 10% < 85%

✓ needs_info needs info 90% ≥ 85%

✓ label bug 92% ≥ 80%, confidence 89% ≥ 60%

#11469 Video matting: Allow setting default… feature

· spam spam 3% < 90%

· duplicate duplicate 22% < 85%

· needs_info needs info 79% < 85%

✓ label feature 100% ≥ 80%, confidence 100% ≥ 60%

6 of 6 would get a label · 0 left alone

237 ms average · $0.00033 total

Those two rows are the whole design: 90% cleared the needs-info threshold, 79% did not, and the second issue was left alone on that question.

It reads issues and writes nothing — there is no --apply. Use --limit,

--labels with your own taxonomy, and --json to pipe it somewhere.

# .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 firstGet 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.

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.

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

- 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 applyis false.

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

npm test # the decision layer is pure; the thresholds are tested offlineNo build step and no dependencies — the action runs the files in src/ directly

on Node 20.

MIT