Skip to content

feat(pr-pipeline): let a trusted fix request name a same-plugin file to edit - #8470

Merged
kyle-sexton merged 10 commits into
mainfrom
feat/pr-pipeline-fix-request-named-path
Oct 20, 2026
Merged

kyle-sexton merged 10 commits into
mainfrom
feat/pr-pipeline-fix-request-named-path

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

Closes #8461

Hold: a human merges this

Summary

A fix request by a trusted person with write access can now name files to add to its review thread's edit scope. A named file joins the scope only when it is in the same plugins/<p>/ directory as the thread's path. The owner decided this on #8461. The case that prompted it is #8116's thread PRRT_kwDOTCGFQM6tojDu: the owner's request on write-transcript.js named run-transcript.js, and the lane refused that file at 2026-10-19T02:31Z as outside every kept thread's scope.

Fix

  • address-feedback.mjs select: for each kept thread, reads the fix requests (isFixRequest) that write-access requesters (fixers) wrote. It takes each file-shaped word outside fenced code and quoted lines, with wrapping punctuation and a :line suffix removed. A word with a / is read from the repository root; a bare file name is read from the thread path's directory. A path is kept as one of the thread's named_paths when namedInScope allows it and the GraphQL tree at the gated head lists it as a regular file (mode 100644 or 100755, exact name). At most 10 per thread.
  • thread-scope.mjs:
    • namedInScope requires plain ASCII segments in normal form, the thread's own plugins/<p>/ prefix (case-sensitive), no .github/ and no instruction path.
    • inThreadScope(path, threadPaths, threads) accepts a path in a thread's named_paths that namedInScope allows for that thread.
    • The instruction-path list moved here from apply-ci-fix.mjs (isInstructionPath), so fix-ci and thread scope share one list.
  • The promote step and the collect step in pr-run-activity-write.yml, the reply job (publish-feedback.mjs, which validates named_paths in run.json) and the check-paths/record jq copy in feedback-thread.sh all apply the same rule.
  • Hardening from the security review:
    • The promote step refuses a lane branch that adds or changes a symbolic link or gitlink, read from the lane tip's tree modes.
    • Instruction paths now match a bare hooks, .claude or .claude-plugin segment, and also cover a plugin's monitors/ and bin/ and .lsp.json.
    • Thread scope refuses any path with a character outside ASCII letters, digits and _@+.-. That closes the JS-versus-jq case-folding gap for characters such as U+212A KELVIN SIGN; no tracked path today has such a character.
    • The plugin-wide part of a thread's scope also refuses a plugin's .mcp.json, .gitmodules and CODEOWNERS, and .bash, .zsh, .ps1, .cjs, .mts and .cts files.
  • Docs: docs/conventions/pr-pipeline/pr-run-activity.md (where thread scope is described), comments-ci.md, and the fix-ci instruction lists in README.md, pr-pipeline.yaml and pr-pipeline.schema.json. Fragments are in .changes/pr-pipeline/ and .changes/source-control/.

Verification

Related

🤖 Generated with Claude Code

kyle-sexton and others added 2 commits October 19, 2026 12:24
…to edit

A write-access fix request on a kept review thread may name files; the
select step records each one that sits in the thread path's plugins/<p>/
directory, is in normal form, is not under .github/ or an instruction path,
and is a regular file at the gated head, as the thread's named_paths.
thread-scope.mjs, the promote step, the reply job and check-paths accept
changes to them. The instruction-path list moves from apply-ci-fix.mjs into
thread-scope.mjs and now also bounds the plugin-wide part of a thread's
scope, so a plugin's .mcp.json, .gitmodules or CODEOWNERS is refused there.

Closes #8461

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and non-ASCII paths

From the security review of the named-path change:

- The promote step refuses a lane branch that adds or changes a symbolic
  link or gitlink, read from the lane tip's tree entry modes.
- Instruction paths match a hooks, .claude or .claude-plugin segment with
  nothing after it, and also cover a plugin's monitors/ and bin/ and
  .lsp.json, for apply-ci-fix and thread scope alike.
- Thread scope refuses any path outside plain ASCII segments, so JS and the
  jq copy agree and no case-folding lookalike reaches a checkout, and its
  plugin-wide part refuses more script and code extensions.

Refs: #8461

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

chatgpt-codex-connector Bot commented Oct 19, 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-19T17:06:30.003724Z 3592533 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 19, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 6d6ed2d922d4f02a1b63281696cdd4d47210e0a7: 0 finding(s), 0 as inline comments.

Security review (incremental, 3b1f117 → 6d6ed2d)

No security issues found.

The incremental change adds TREE_READ_CAP (20) to namedPaths in .github/scripts/pr-unblock/address-feedback.mjs. The cap bounds the distinct directories whose trees are read at headSha for one thread. A comment cannot make the lane read an unbounded number of trees by naming paths in many different directories.

  • It narrows the surface. Before this change a comment could name many paths in many directories, and each new directory cost one listTree call. Now at most TREE_READ_CAP (20) trees are read per thread. Candidates in directories past the cap are skipped with continue, and no further path is added without an existence check.
  • The trust checks are unchanged. The author must be a trusted human and a write/admin fixer. The comment must be a fix request. namedInScope and the regular-file mode check (100644/100755, so no symlinks or gitlinks) still apply. The skip path only drops candidates, never admits one without the check, so scope cannot widen.
  • The test change (address-feedback.test.mjs) only adds coverage for the cap and has no security surface.

No instruction-surface deletions in this diff, so the protected-class register was not needed.

Comment thread .github/scripts/pr-unblock/address-feedback.mjs Outdated
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 6d6ed2d922d4f02a1b63281696cdd4d47210e0a7: 0 finding(s), 0 as inline comments.

No findings. I read the incremental diff (address-feedback.mjs and address-feedback.test.mjs) and found nothing to block. I could not run the test suite: the sandbox needs approval for node --test, and it was not granted. The checks below come from reading the code and the test fixtures.

Change reviewed. namedPaths now stops reading new directory trees once TREE_READ_CAP (2 × NAMED_CAP, so 20) distinct directories have been read. After that, a candidate in an unread directory is skipped with continue. A candidate in a directory that was already read is still checked against the cached tree (address-feedback.mjs:191-199).

What I checked.

  • Cap applies only to new directories. The cap test sits inside if (!trees.has(parent)), so it limits only first reads. A name in a directory already read still counts toward NAMED_CAP.
  • Skipped candidates cannot be named. The continue runs before trees.get(parent), so a skipped candidate never reaches the .find call. Skipping fails closed: a path in an unread directory is never added to the scope.
  • Loop exits are unchanged. The NAMED_CAP early break still works as before.
  • New test (address-feedback.test.mjs:732-743). I traced it against the TREES fixture (lines 639-644) and the named helper (lines 645-655).
    • With 25 missing directories plus run-transcript.js, the lane makes 20 reads (the 20 missing directories, in candidate order) and skips the rest. named_paths is undefined and reads.length is 20, as asserted.
    • With run-transcript.js listed first, the lane reads T and then 19 missing directories (20 reads). It skips the remaining missing directories. run-transcript.test.js is then found in the cached T tree, so both names are kept and reads.length is still 20, as asserted.

I did not review the other files of this pull request, which are unchanged since the last review.

@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 19, 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: 3592533dc8028c4d411151b79ba658cd57a057a8 (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=8470 and activity=resolve-conflicts:

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

kyle-sexton and others added 5 commits October 20, 2026 06:05
address-feedback.mjs: keep main's past-the-cap fix-request round and
attach named paths to the threads that round works.
pr-pipeline.schema.json: take main's prepare description, which moved
the refused-path list to the patch rules; the list's monitors/, bin/ and
.lsp.json additions stay in README.md, pr-run-activity.md and
pr-pipeline.yaml.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
…d file

A fix request naming _utils.js read as utils.js, and __init__.py as
init__.py, so the lane could name in a file the requester did not name
or drop the one they did. Strip _ only as a matched pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
@kyle-sexton

Copy link
Copy Markdown
Member Author

Review note for the merge (from GitHub Security & Cost's pass at 9c2af24, P5, not blocking): pathTokens reads every line of the fix request, so a line like "don't touch scripts/x.sh" adds that path to the named scope. A follow-up should read paths only from the fix-request line or a marked paths: line.

Comment thread .github/scripts/pr-unblock/address-feedback.mjs Outdated
@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 20, 2026
Words that pathTokens keeps but that are not files (obj.field, v1.2) took
slots in the 10-path cap before the tree check, pushing a real named file
out. Check every candidate and stop once 10 existing files are confirmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
Comment thread .github/scripts/pr-unblock/address-feedback.mjs
With the cap counting only existing files, nothing limited listTree calls:
one per distinct parent directory, bounded only by comment length. Stop
reading new directories after TREE_READ_CAP (2 x NAMED_CAP); names in
directories already read still count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit d70c393 Oct 20, 2026
25 checks passed
@kyle-sexton
kyle-sexton deleted the feat/pr-pipeline-fix-request-named-path branch October 20, 2026 14:08
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.

pr-pipeline: let a trusted fix request widen a thread's edit scope to a named file in the same plugin

1 participant