Skip to content

feat(pr-pipeline): refine thin issues at intake triage - #9310

Merged
kyle-sexton merged 14 commits into
mainfrom
feat/9215-thin-ticket-enrichment
Oct 23, 2026
Merged

kyle-sexton merged 14 commits into
mainfrom
feat/9215-thin-ticket-enrichment

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Closes #9215

Cost decision: #9215 (comment)

Summary

Intake triage now refines a thin issue: one whose verdict flags intent, or whose body has no task list. A read-only model run explores the repository, searches issues and pull requests, and reads the allowed docs. The lanes App then posts one marked refinement comment. It never edits the issue body.

  • Exact: the comment lists the outcome and its criteria as a - [ ] task list. A status: needs-decision label that the lanes App set is lifted, and triage runs once more so the issue can be admitted. A needs-decision label set by a person stays.
  • Not exact: the comment asks numbered questions, each with a RECOMMENDED answer and its basis, and the issue waits at status: needs-decision.

Fix

  • .github/scripts/intake-triage/refine.mjs (new): selectRefinement picks the issue and writes the model's context, including pre-fetched duplicate candidates. validateOutput checks the model output against a closed schema and drops related work that is not a fetched candidate. render keeps model text from forging the marker, a mention, a task-list line or an extra line. applyRefinement posts the comment and sets labels.
  • admit.mjs decide(): criterion 7 also counts - [ ] lines from the lanes App's own refinement comment, matched by account id, not login.
  • intake-triage.yml: four jobs: refine-select, refine-issue (the model, with contents read only), apply-refinement (App token) and redispatch-refined (dispatch grant only).
  • Re-triage after an exact refinement happens at most once per issue. It is keyed on the refinement marker: applyRefinement writes nothing on an issue the App already refined.
  • Security review fixes: only issues and pull requests a listed human or the lanes App opened reach the model as duplicate candidates, so an outsider's issue title cannot steer the refinement. line() also escapes [, ], ( and ) and breaks GH-N references and ://. Criterion 7 counts a refinement only when it is newer than the body's last edit. The work lane's issue.json keeps the refinement as author_kind: refinement, so the lane sees the criteria that admission counted (/work-items:work documents the new kind).
  • The model job reuses the existing triage-issue route, because the broker's pinned config resolver rejects a new route name. Config and funding entries and a changelog fragment are included.

Verification

  • node --test .github/scripts/intake-triage/refine.test.mjs .github/scripts/intake-triage/admit.test.mjs: 75 passed. This includes a test that a second exact refinement re-runs no triage and writes nothing, and tests that look-alike refinement comments from another author are ignored.
  • After the security fixes, node --test on the intake-triage and write-issue suites: 172 passed. This includes new tests that fail without the fixes: an outsider's candidate is dropped, link and reference escaping holds, a refinement older than a body edit is refused, and the work lane keeps the refinement.
  • check-affected --tests: 12 passed, 2 skipped. actionlint and zizmor are clean on intake-triage.yml.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 6 commits October 23, 2026 00:14
refine.mjs decides whether an issue is thin, writes the context a
read-only model run reads, and posts the run's structured output as a
separate lanes App comment. admit.mjs counts the task list in that
comment for criterion 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refine-select picks a thin issue with the job's read token, refine-issue
runs the model with contents read only, apply-refinement posts its output
through the existing intake self-job mint, and redispatch-refined triages
an exact refinement once more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… code span

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only issues and pull requests a listed human or the lanes App opened
  reach the refinement model as duplicate candidates.
- Refinement text cannot add a markdown link, a URL, or a GH-N reference.
- Criterion 7 counts a refinement only when it is newer than the body's
  last edit, and the work lane's issue.json keeps it as refinement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 23, 2026 04:49
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T04:52:17.405033Z 3307ea3 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review needs no new review of 140ceff9468aa956aa09c7a6d12b58fb53596fa3: no file of this pull request changed since the review of b799061.

No security issues in the incremental change (admit.mjs and admit.test.mjs, since d3923e8).

critical-findings: 0

  • admit.mjs (decide, around line 248): the unrunnable-criteria check now removes U+200B from the joined issue body and refinement lines before it runs. render in refine.mjs breaks :// with a zero-width space so links don't render, which had hidden a URL like https://example.org from the host check. Removing the character only makes the check stricter, so it can't be used to slip a criterion past the check. The change only widens what decide refuses, and it cannot admit anything that was refused before.
  • Invisible characters other than U+200B: a human-authored issue body could carry other invisible characters (U+200C, U+2060, U+FEFF). That gap was already there before this change, and the change doesn't open or widen it. The issue author must be a listed human or the lanes App to be admitted at all. The model's refinement text is rendered by render, which uses only U+200B. No exploit path exists in this diff.
  • admit.test.mjs: the new test runs a real render and validateOutput round trip. It checks that https://example.org in a rendered refinement is still refused, which is the regression this change guards against. Test-only change.

Manifest items: none touched in this increment. No instruction-surface deletions. No trust-gate paths touched.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3307ea3e6a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/intake-triage/admit.mjs Resolved
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

claude-review needs no new review of 140ceff9468aa956aa09c7a6d12b58fb53596fa3: no file of this pull request changed since the review of b799061.

Incremental review of .github/scripts/intake-triage/admit.mjs and admit.test.mjs since d3923e8. I found nothing to report.

What changed. decide() now strips U+200B from the issue body and refinement text before unrunnableCriteria reads them. Before this, render() in refine.mjs broke :// and www. with a zero-width space, so a refinement criterion such as "the page at https://example.org/spec says nineteen" slipped past the unrunnable-URL check. The new test, "a rendered refinement criterion that reads a page outside the lane's web hosts is still refused", renders a real refinement through render(validateOutput(...)) and asserts the issue is refused with the host named.

What I checked by reading the code. I did not run the tests.

  • The strip applies to the joined text that feeds only the unrunnable check. The task-list presence check at admit.mjs:243 still reads the unstripped lines, so it is unaffected.
  • line() in refine.mjs:213 breaks :// and www. with a zero-width space, and breaks @ and #N/GH-N references the same way. Stripping restores the URL host for the check. The backslash escapes on [ ] ( ) do not change the parsed host.
  • The strip also applies to a human-written issue body. A zero-width space inside a URL host there is now read as the plain host, which can only make the check stricter, not looser.
  • noteUnrunnable (admit.mjs:336) reads only issue.body, never refinement text, so it has no matching gap.
  • The test helper refinement(app, body) already accepts a body argument, and render and validateOutput are exported from refine.mjs.

Closing issue #9215. The incremental change does not alter how the three criteria are met. The refinement comment, the read-only run with a script-side write, and status: needs-decision for a non-exact refinement were reviewed at the earlier head.

Hold. No hold class applies to these two files. The cost acceptance for #9215 is recorded in the issue comments.

Manifests. The change touches no plugin manifest, so no manifest <field> lines apply.

…e labels

- applyRefinement skips an issue whose title or body changed since the
  context the model read.
- Labels change before the marker comment, so a failed label update leaves
  no marker and a later refinement can finish.
- line() also breaks `www.` autolinks.
- The admit doc comment sits above admit again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed add now leaves the issue at needs-decision instead of with
neither label, so the sweep still reaches it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread .github/lane-funding.json Resolved
Comment thread .github/workflows/intake-triage.yml Resolved
@melodic-automation-lanes melodic-automation-lanes Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 23, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

The pr-unblock lane stopped on this pull request and needs a person: the merge stages a change under .github/workflows/, which the lane does not push.

  • Reason: workflow-staged
  • Resume key: e8b75efbedb720e1a4a82f092bcb88d38d003707 (the head the lane stopped on)

To resume, deal with the reason above, remove the needs-human label (and do-not-merge, if set), then dispatch the pr-unblock workflow with pr-number=9310 and activity=resolve-conflicts:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=9310 -f activity=resolve-conflicts

@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 23, 2026
kyle-sexton and others added 2 commits October 23, 2026 09:19
Keeps both sides of admit.mjs: decide() takes the refinement comments,
bodyEditedAt and webHosts, and the unrunnable check reads the refinement's
task list with the body; noteUnrunnable passes comments to sayWhy. The
refine-issue funding entry gains triggers and gate, and the gate test
recognizes refine-select's author gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An exact refinement of a status: ready issue without agent-ready or
needs-decision adds needs-triage and asks for one more triage, so
admission reads the new criteria. The marker still allows it once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on d3923e85f9fbe68db5823d803890d3d5cc08a496; the summary is in this lane's pull request comment.

claude-lane: claude-review

@github-actions github-actions Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

claude-review holds this pull request for a person (cost) at d3923e85f9fbe68db5823d803890d3d5cc08a496.

Adds a new model run: the refine-issue job in .github/workflows/intake-triage.yml (claude-code-action, 30-minute timeout, AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN) and a refine-issue entry in .github/lane-funding.json. It starts on every issues/workflow_dispatch triage of a thin issue. The #9215 owner decision (Q50) already accepts this cost.

claude-lane: claude-review

kyle-sexton and others added 2 commits October 23, 2026 09:27
…ritten

line() breaks :// and www. with a zero-width space, which hid a refined
criterion's outside-host page from unrunnableCriteria. decide() now
strips the break before that check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

ci-status removed needs-human: every review-lane hold since a trusted person last removed it is cost-only, and the owner recorded the cost decision in #9215 (comment). A person adding needs-human back holds it again.

@github-actions github-actions Bot removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 23, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 6468097 Oct 23, 2026
35 checks passed
@kyle-sexton
kyle-sexton deleted the feat/9215-thin-ticket-enrichment branch October 23, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(pr-pipeline): enrich a thin ticket with read-only explore and research before admission

1 participant