feat: automatic issue triage over free GitHub Models (v2.1.0) - #11
Merged
Merged
Conversation
New issues are classified by GitHub Models (free in Actions over the
GITHUB_TOKEN, no secret) and get advisory labels plus a summary comment,
applied deterministically. Triage never closes, assigns, or merges — a human
stays in the merge path.
Safe by construction: the issue-triage workflow holds issues:write +
models:read + contents:read and nothing else, so a prompt injection in an issue
body cannot reach code, a secret, or a merge. The body goes to the model as
untrusted data in a separate user message wrapped in <issue_body>; the model's
label suggestions are intersected with the repo's live label set (gh label
list), so a hallucinated label is dropped — the workflow never creates labels.
Malformed output or no GitHub Models access falls back to needs:human-triage.
The advisory comment is keyed by an HTML marker and updated in place, so
re-triggers never spam the issue.
Ships with issue templates (bug_report, feature_request, config), a
seed-labels.sh run once at setup to create the keepwright-specific labels
(deterministic, kept out of the triage workflow's blast radius), rule
09-issue-triage.md wired into the CLAUDE.md equalization table, and an optional
config block: "issues": { "triage": "off" | "github-models", "model": "..." }.
Engine: placeholders.ts adds ISSUES_TRIAGE + TRIAGE_MODEL and the issues config
type; the schema gains the issues block; apply.ts walks templates/.github/
ISSUE_TEMPLATE. Bump 2.1.0.
Autor: Leonardo Candiani
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Automatic, advisory issue triage. When an issue is opened/edited/reopened, a classify job asks GitHub Models (free in Actions over the
GITHUB_TOKEN, no secret) for strict JSON — suggested labels, possible duplicate, missing info, severity, summary — and a deterministic apply job acts on it. It never closes, assigns, or merges; a human stays in the merge path.This is the tournament-champion approach ("Zero-Cost Deterministic Triage") — highest value at zero cost with a blast radius that physically cannot reach code, secrets, or a merge.
Safe by construction
issues: write+models: read+contents: readand nothing else. A prompt injection in an issue body can, at worst, suggest a wrong label.usermessage wrapped in<issue_body>; thesystemmessage declares it DATA to classify, never instructions.gh label list). Hallucinated labels are dropped; the workflow never creates labels.needs:human-triagefallback. The advisory comment is marker-keyed and updated in place (no spam on re-trigger).What's included
templates/workflows/issue-triage.yml.template(auto-wired by the existing workflows walker)templates/.github/ISSUE_TEMPLATE/{bug_report,feature_request,config}(+ newapply.tswalker)templates/scripts/seed-labels.sh.template— deterministic, human-run label seeding (auto-wired by the existing scripts walker)templates/rules/09-issue-triage.md.template+CLAUDE.mdpointer (passesvalidate-claude-md-sync)placeholders.ts(ISSUES_TRIAGE,TRIAGE_MODEL,issuesconfig type), schemaissuesblockEMPIRICAL VALIDATION (real environment)
Validated on a fixture (
apply.ts --repo-path):applyproducesissue-triage.yml, the 3 ISSUE_TEMPLATE files,seed-labels.sh, rule 09, and the CLAUDE.md pointer. Placeholders substituted:runs-on: ubuntu-latest(runner=github),if: ${{ 'github-models' == 'github-models' }},--arg model "openai/gpt-4o-mini", ISSUE_TEMPLATE with the real project/maintainer/repo. Theoffvariant generatesif: ${{ 'off' == 'github-models' }}(workflow no-ops).run:blocks passbash -non bash 3.2 (strictest) and shellcheck-S error(zero errors). YAML valid. (Refactored the system-prompt heredoc out of$(...)to dodge bash 3.2's heredoc-in-command-sub bug; fence-stripping uses octal\140instead of literal backticks.)admin, keeps canonical casing, dedups →bug,enhancement; malformed JSON → fallback;canon_labelcase-insensitive returns repo casing.tsc --noEmit --strictzero type errors;applyidempotent (41 skipped on 2nd run, 0 created/updated); config validates against the schema; local replica of CI's "templates exist" + "no secrets" both pass.Runtime smoke (a real issue opened on GitHub triggering the live model call) is the maintainer's to run after merge — it needs the deployed workflow +
models:readon the repo.