fix(standards): scope from HEAD^1 so private repos stop sweeping whole-repo - #171
Merged
Merged
Conversation
…e-repo
Changed-files scoping has never worked on a private repo. "Resolve scope"
narrowed the run by fetching the PR base:
git -C repo fetch --depth=1 origin "${BASE_SHA}"
but the checkout above sets persist-credentials: false, so no credential
remains. A public repo serves that fetch unauthenticated and scopes
correctly; a private repo cannot, and the step falls back to a whole-repo
sweep -- correctly and loudly, but on every PR, forever. Measured: kebab-tax
and financial-agent (private) fall back; amelia-boone, archive-resolver and
dotfiles (public) scope. Clean split on visibility, 9 repos affected.
Take the base from the local commit graph instead. For a pull_request event
HEAD is refs/pull/N/merge, whose first parent IS the base, so fetch-depth: 2
makes HEAD^1 the diff target -- no network call, no credential, and
persist-credentials: false stays as-is (it is deliberate hardening against
zizmor's artipacked, not an oversight to route around).
HEAD^1 is also strictly more accurate than the event payload's base.sha,
which goes stale when the base branch moves after the event fires. HEAD^1 is
the base the merge ref was actually computed against.
Assert the merge shape rather than assume it. On a NON-merge HEAD, HEAD^1
still resolves -- to the previous commit in linear history -- which would
scope the run to one commit's files instead of the PR's. That false-narrow is
worse than sweeping, because it is a green check over too few files. The
parent count is now checked explicitly and anything that is not a merge ref
falls back to the sweep.
Verified against three fixture repos: a real merge ref at depth 2 scopes to
the base and excludes pre-existing debt; a non-merge HEAD sweeps; a root
commit sweeps. actionlint, yamllint and zizmor clean.
Closes #170
Claude-Session: https://claude.ai/code/session_017s2qrmkQaV54fbcMzB2KRm
twistedmelonman
deleted the
claude/fix-private-repo-scoping-dc0d321a
branch
September 18, 2026 01:45
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.
Problem
Changed-files scoping has never worked on a private repo. Every PR in one is linted whole-repo and fails on debt its author never touched — precisely what the scoping exists to prevent (
standards-check.yml:25-31).Cause
"Resolve scope" narrowed the run by fetching the PR base:
git -C repo fetch --depth=1 origin "${BASE_SHA}"The checkout immediately above sets
persist-credentials: false, so no credential remains. A public repo serves that fetch unauthenticated and scopes correctly. A private repo cannot:and the step falls back to a whole-repo sweep — correctly and loudly, but on every PR, forever.
nightowlstudiollc/kebab-taxnightowlstudiollc/financial-agentnightowlstudiollc/amelia-boonesmartwatermelon/archive-resolvertwistedmelonman/dotfiles9 private repos carry a standards-check caller and are affected.
Fix
Take the base from the local commit graph instead of the network.
For a
pull_requesteventHEADisrefs/pull/N/merge, whose first parent is the base.fetch-depth: 2makesHEAD^1the diff target — no fetch, no credential, andpersist-credentials: falsestays exactly as it is. That setting is deliberate hardening against zizmor'sartipacked, not an oversight to route around.HEAD^1is also strictly more accurate than the payload'sbase.sha, which goes stale if the base branch moves after the event fires.HEAD^1is the base the merge ref was actually computed against; when they differ the run now says so.The subtle part
On a non-merge HEAD,
HEAD^1still resolves — to the previous commit in linear history. Scoping to that would lint one commit's files instead of the PR's: a false-narrow, and a green check over too few files is worse than a noisy sweep. So the merge shape is now asserted, not assumed via parent count, and anything else falls back to the sweep.Verification
Three fixture repos, exercising the real logic:
debt.mdexcludedactionlint,yamllint(canonical config) andzizmorall clean. Local code-reviewer, adversarial-reviewer and codebase reviewer all PASS.This PR cannot prove itself.
github-workflowsis public, so its own run scopes either way. The fix is only proven by a private repo loggingscoping to files changed since—nightowlstudiollc/kebab-tax#1270is the intended first check, after thestandards-check-v1tag moves.Closes #170
https://claude.ai/code/session_017s2qrmkQaV54fbcMzB2KRm