Skip to content

feat(claude-lanes): repin only the ci-workflows paths a release changed - #884

Merged
kyle-sexton merged 3 commits into
mainfrom
feat/repin-changed-paths-only
Oct 26, 2026
Merged

kyle-sexton merged 3 commits into
mainfrom
feat/repin-changed-paths-only

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

Refs: melodic-software/claude-code-plugins#10151

Summary

Owner decision (2026-10-25): a ci-workflows repin moves only the pinned paths a
release actually changed, not every pin, and the existing repin tool
(components/claude-lanes/repin-callers.sh) stays the single source of truth for
repins.

Evidence:

Fix

repin-callers.sh apply now decides each melodic-software/ci-workflows/<path>@<sha> ref
separately:

  • One compare per distinct pinned SHA, compare/<pinned>...<release>, cached for the
    run. The same response feeds the downgrade fence and the file list. The fence is
    unchanged in meaning: a fallback-form pin the release does not contain (behind or
    diverged in this direction) is left as is.
  • Moved: the release changed the reusable workflow file, anything under the composite
    directory (tests included), or a path the pin reaches through a commit-relative
    uses:. Those are a step-level $/<dir> action or a job-level
    ./.github/workflows/<file> nested workflow, the two forms runner-policy diffs. Each is
    read once from the release, transitively. intake-triage, for example, runs
    $/.github/actions/run-claude-cli and report-lane-outcome, so a change to those moves
    intake-triage too.
  • Fail closed, moving every pin at that SHA, in two cases: the compare lists 300 or
    more files (the documented cap), or it changed a file that is not a test (*.test.*),
    not a README.md, not under docs/, and not reached by any pin (for example a shared
    .github/scripts/*.cjs or schemas/). That is today's behavior.
  • Unchanged pins stay byte-identical. Moved pins get the release SHA with the
    # vX.Y.Z comment. The version note lists what moved and what stayed.
  • --root <dir> runs the same tool against any repository. It discovers every
    git-tracked .github/ YAML file that pins ci-workflows, so a consumer checkout such as
    claude-code-plugins repins with this tool instead of hand edits. The rename map is read
    from beside the script. The standards lane's invocation (no --root, enumerated
    callers) is unchanged. The apply step now gets GH_TOKEN, because it calls gh on any
    day a new release resolves.

repin-policy-lockstep.mjs plans only the pins apply moved, so an unchanged reusable pin
keeps its existing contract and needs no contract copy. Its own caller rewrite
(rewriteCallerFiles) is removed: it rewrote every pin in its target files to the new
SHA, which would have undone the selective repin, and apply already rewrites those files.
Its two unit tests go with it.

Out of scope and unchanged: no runner-policy contract rule is loosened, nothing
auto-admits a secret-bearing runner-input contract, and components/runner-policy/policy.json
semantics are untouched.

Verification

Run from the worktree root:

  • bash harness/shell/run-tests.sh components/claude-lanes/repin-callers.test.sh: pass,
    161 assertions (the existing 129 plus 32 new). New cases:
    • only pr-review-cli.yml changed moves only that ref, with one compare call;
    • a shared-script change moves all refs;
    • a composite-directory change moves only that composite;
    • a composite reached through a nested workflow moves the reusable;
    • an inert-only change moves nothing, while the same files at the 300-file cap move
      everything;
    • two distinct SHAs cost two compares;
    • the standards lane's own invocation moves only the changed lane caller.
  • Mutation check: forcing every decision to move, dropping fail-closed, dropping reach,
    dropping the cap, dropping the compare cache, counting tests, or dropping the directory
    prefix rule each turns new cases red. Inverting the fence statuses turns 20 assertions
    red.
  • node --test components/claude-lanes/repin-policy-lockstep.test.mjs: 23 pass. The two
    new tests (only moved pins are planned; settleLockstep leaves an unmoved caller pin
    alone) fail against the previous script.
  • bash harness/shell/run-tests.sh components/claude-lanes/claude-lanes.test.sh and
    node --test components/runner-policy/runner-policy.test.mjs (355): pass.
  • shellcheck --rcfile .shellcheckrc, biome, actionlint and zizmor on the changed files:
    clean.
  • End to end against the live v0.65.13 compare on a copy of claude-code-plugins'
    .github/ from before #10151 (442 files, 47 pins):
    • Real snapshot: 46 of 47 pins move, because none of the pinned SHAs is v0.65.12. The
      lane pins were at v0.65.11, and v0.65.11...v0.65.13 also changed
      schemas/lane-outcome.v1.schema.json, so they fail closed exactly as #10151 moved
      them. The v0.55, v0.58 and v0.59 pins fail closed on .github/scripts/*.cjs and
      .claude/rules/*.md.
    • Synthetic variant with every pin set to v0.65.12: only the two pr-review-cli.yml
      lines move, and the other 45 stay byte-identical.

Consumer command, from any directory with an authenticated gh, jq, GNU sed and bash
4.4 or newer:

GITHUB_OUTPUT=/tmp/repin.out bash <standards-checkout>/components/claude-lanes/repin-callers.sh resolve melodic-software/ci-workflows
GITHUB_OUTPUT=/tmp/repin.out bash <standards-checkout>/components/claude-lanes/repin-callers.sh apply <tag> <sha> <date> --root <consumer-checkout>

resolve writes tag, sha and date to the output file. apply leaves the moved pins
in the consumer's working tree and writes changed, old-sha, old-tags and
version-note.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn

repin-callers.sh apply now moves a ci-workflows pin only when the target
release changed what it runs: the reusable workflow file, anything under
the composite action directory, or a path either reaches through a
commit-relative `$/` action or `./.github/workflows/` nested workflow.
It decides from one cached compare per distinct pinned SHA. It fails
closed and moves every pin at that SHA when the file list is at the
300-file cap, or when a non-test, non-README, non-docs/ file that no
pinned path reaches changed.

apply --root <dir> runs the same tool against any repository's tracked
.github/ callers, so consumer repins stop being hand edits. The standards
lane's invocation is unchanged.

The lockstep plans only pins apply moved and no longer rewrites callers
itself, so an unchanged reusable pin needs no contract copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 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-26T04:11:14.775551Z b7f1755 PR opened
ℹ️ 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 26, 2026 •

Copy link
Copy Markdown

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

No security findings in the incremental change (e13ee5b..ca1b758).

I reviewed the three changed files. I read .claude-lane/incremental.diff and the surrounding repin::apply code in components/claude-lanes/repin-callers.sh.

  • repin-callers.sh:
    • The new relabel set (pins already on the release SHA whose comment isn't # <tag>) is built from refs, so it only holds paths and SHAs that repin::apply has already checked. Every distinct path is matched against UPSTREAM_PATH_RE in the decision loop before this code runs.
    • A relabeled pin keeps the same commit SHA. Only its trailing comment is rewritten, so the code a caller runs doesn't change.
    • The comment text from refs is used only for the $4 != t comparison. It is never put into the sed program, the shell, or GITHUB_OUTPUT.
    • tag and sha are not validated in repin::apply itself. They come from resolve, which only accepts a tag matching ^v[0-9]+\.[0-9]+\.[0-9]+$ and a SHA from the GitHub API. Both are used the same way as before this change.
    • The old_sha fallback to the release SHA is a value from that same resolved set. The comment says lockstep treats it as a no-op.
    • The existing post-rewrite checks still apply: the expected-versus-actual count of new pins, and the check that nothing outside the caller set was modified.
    • The ahead-of-release "fenced" set and the path allowlist are untouched, so a pin that is ahead of the release is still never rewritten.
  • repin-callers.test.sh: this only adds a test fixture for the relabel case. It has no security surface.
  • runner-policy.test.mjs: the convergence assertion is now per upstream workflow path instead of one SHA across all callers. Only the test changes, and the production pin-registration checks are unaffected. This is not a security weakening.

No trusted-path globs or plugin manifests are in the diff, and no standing instruction is deleted, narrowed or softened. I did not consult the instruction-exception register, because the diff doesn't touch an instruction surface.

critical-findings: 0

@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: b7f175541e

ℹ️ 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 components/claude-lanes/repin-callers.sh
Comment thread components/claude-lanes/repin-callers.sh
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

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

No findings in the incremental change (e13ee5b..ca1b758). I could not run the tests: the sandbox required approval for run-tests.sh and I did not bypass it. This is a read-only review of the diff and the surrounding script.

repin-callers.sh

  • A pin already on the release SHA whose comment is not # <tag> (a fallback-form comment, an older tag, or no comment) now joins moved and takes the tag form. Pins already @<sha> # <tag> are excluded.
  • expected skips pins that already carry @<sha> # <tag>, so the post-rewrite count of newly pinned lines (after - before) stays consistent.
  • A run that only relabels pins sets old_sha to the release SHA. repin-policy-lockstep.mjs returns early when every old SHA equals the new SHA (line 331), so lockstep sees a no-op and plans no contract copy.
  • Comments are normalized to vX.Y.Z, fallback or empty, so the $4 != t comparison covers all three non-matching cases. The sed pattern accepts a line ending right after the SHA, so a pin with no comment is relabeled too.

repin-callers.test.sh
The new relabel case asserts the right things: exit 0, the tag form on the release SHA, only that caller modified, and old-sha equal to the release SHA.

runner-policy.test.mjs
The convergence test now groups pins by upstream path and asserts one revision per path. This matches the selective-repin behavior, where the code-review and security-review callers can sit on different revisions. The Map/Set handling is correct because Set.add returns the set.

@kyle-sexton
kyle-sexton enabled auto-merge (squash) October 26, 2026 04:54
…ins on the release

The runner-policy convergence test now requires every caller of one
upstream path to share a revision, since a selective repin can leave the
code-review and security-review callers on different ones.

apply gives a fallback-form pin already on the release SHA the tag form,
as the full rewrite did, and hands lockstep the release SHA when a run
only relabels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn
@kyle-sexton
kyle-sexton merged commit 0a96fd4 into main Oct 26, 2026
22 checks passed
@kyle-sexton
kyle-sexton deleted the feat/repin-changed-paths-only branch October 26, 2026 05:04
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.

1 participant