Skip to content

Fix link check on pushes that have no base commit - #21644

Merged
shoumikhin merged 1 commit into
mainfrom
link-check-base-ref
Aug 7, 2026
Merged

Fix link check on pushes that have no base commit#21644
shoumikhin merged 1 commit into
mainfrom
link-check-base-ref

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

.github/workflows/_link_check.yml decides which commits to diff by inspecting the event that triggered its caller. That is the wrong place to make the decision. A reusable workflow is called from more than one context, and the value it reaches for is not present in all of them.

For a pull request it uses the pull request base. For everything else it uses github.event.before, which is only meaningful for a push to a branch that already existed.

trigger github.event.before before this change
pull request not used, PR base instead changed lines checked
push to an existing branch previous branch tip changed lines checked
push creating a tag all zeros job fails, exit 128
push creating a branch all zeros job fails, exit 128
scheduled run absent whole tree checked, by accident
manual dispatch absent whole tree checked, by accident

The all-zero rows fail outright, which is what every ciflow/nightly/* tag push hits:

git fetch --no-tags --depth=1 origin 0000000000000000000000000000000000000000
fatal: remote error: upload-pack: not our ref 0000000000000000000000000000000000000000
##[error]Process completed with exit code 128

The two "by accident" rows are worth spelling out, because the behavior there is correct but nothing makes it so. With before absent, the fetch becomes git fetch --no-tags --depth=1 origin "", which quietly succeeds by fetching the default head. Then in

./scripts/lint_urls.sh $(
  ...
  echo "${{ github.event.before }}" "${{ github.sha }}"
)

the unquoted command substitution word splits the empty first field away, so the script gets one argument rather than two, fails its [ $# -eq 2 ] test, and falls through to whole tree mode. The right thing happens for the wrong reason, and only for as long as that substitution stays unquoted.

Fix

Take the base as an input instead:

      base_ref:
        description: Commit to diff against. Empty, or all zeros, means check the whole tree.
        type: string
        required: false
        default: ''

Empty means there is nothing to diff against. The three lint scripts already support that: given two arguments they diff a range, given none they scan the whole tree. So the empty case needs no fallback logic, it just calls them with no arguments. The all-zero SHA maps to empty, because a caller forwarding github.event.before has no way to avoid producing it.

With the base known up front, the separate Fetch base ref step has nothing left to decide, so it folds into the lint step. Each job loses a step and the file gets shorter, 107 lines to 100:

          args=()
          # A push creating a tag or branch reports an all zero SHA no remote can serve.
          if [ -n "$BASE_REF" ] && [ "$BASE_REF" != "0000000000000000000000000000000000000000" ]; then
            git fetch --no-tags --depth=1 origin "$BASE_REF"
            args=("$BASE_REF" "$HEAD_REF")
          fi
          ./scripts/lint_urls.sh "${args[@]}" || {

lint.yml passes the pull request base on pull requests and github.event.before otherwise, one added line.

The job level if: conditions still read github.event_name and the pull request labels. Those decide whether a job runs at all, which is a different question and out of scope here. What changes is how the diff base is computed.

One incidental correctness gain: the old steps checked out inputs.ref but diffed against github.sha or head.sha read from the caller's event. Now the checked out commit and the diff head are the same input, so they cannot drift apart. That mismatch was not hypothetical, see below.

What nightly.yml stops running, and why that is also a fix

nightly.yml now runs the check only on the schedule and on a manual dispatch. That drops two triggers: pull requests touching .github/workflows/nightly.yml, and ciflow/nightly/* tag pushes.

The pull request trigger was not providing coverage. nightly.yml passes ref: ${{ github.sha }}, which on a pull request is the merge commit. The head commit is therefore never checked out and never fetched, so git diff base..head cannot resolve:

Checking changed files between bcceab12eff66b5bdade7b0c9dc1cb30ef694791..7e66c47dd31a7259a15eaf08e4060208dcbfdde3
fatal: Invalid revision range bcceab12eff66b5bdade7b0c9dc1cb30ef694791..7e66c47dd31a7259a15eaf08e4060208dcbfdde3

lint_urls.sh and lint_xrefs.sh consume that diff through a process substitution, so the failure is swallowed and they report success having checked zero URLs. lint_file_size.sh reads it into a plain assignment, which set -e turns into a hard failure, and there is no skip-file-size-lint label to escape it. Run 31125514863 shows all three: two green jobs that examined nothing, and one red one.

So this removes two no-ops and one permanently red, unskippable check. Pull requests keep the real check from lint.yml, against their own base, on the path where the head commit is actually checked out.

The tag push trigger is a policy call, not a bug fix. To be precise about causation: the exit 128 there is removed by the zero-SHA guard plus the empty default, not by the if:. Left alone, a ciflow/nightly/* push would now run a clean whole tree scan. It is skipped because there were 19 such tag pushes in the last day alone, a whole tree URL scan takes about six minutes, and it would be red on the pre-existing dead links every time.

One narrow gap remains. A pull request targeting the nightly branch gets no link check at all, since lint.yml excludes that branch through branches-ignore. Before this change it got the two no-ops and the red file-size job, so nothing that worked is lost. Happy to add a correct pull_request arm to nightly.yml if reviewers would rather close it.

Result

trigger after this change
pull request, via lint.yml changed lines only, unchanged
push to main or an existing release/* changed lines only, unchanged
push creating a release/* branch whole tree, instead of exit 128
push of a ciflow/nightly/* tag skipped, instead of exit 128
pull request touching nightly.yml skipped, was two no-ops and one red job
scheduled nightly whole tree, unchanged, now by design
manual dispatch, either workflow whole tree, unchanged

lint.yml declares no tags: in its push trigger, so the tag case only ever reached _link_check.yml through nightly.yml.

Testing

  • All three workflow files parse, and every run block in _link_check.yml is clean under shellcheck -S style.
  • Ran the rewritten lint step as a standalone script for every shape of input, across all three jobs: a real base SHA fetches and passes two arguments; an all-zero SHA and an empty value skip the fetch and pass none; an unservable base exits 128 with git's own message and never reaches the lint script. Repeated with and without set -u, and with stray positional parameters already set, so the argument list cannot depend on how the runner invokes the step.
  • Ran scripts/lint_file_size.sh with no arguments to confirm the whole tree path works end to end: 9033 files checked, no failures.
  • The whole tree paths of lint_urls.sh and lint_xrefs.sh use git grep -P, which needs a git built with PCRE support that I did not have locally, so I did not run those myself. They are already exercised in CI though, by the current scheduled nightly, which reaches whole tree mode through the accident described above. Its most recent lint-urls log checks about 2148 URLs and reports 2126 OK, 15 WARN, 7 FAIL. Those 7 are genuine dead links, unrelated to this change, and it neither fixes nor hides them, so the scheduled job stays red until they are updated.
  • On this pull request, Lint runs the changed workflows against themselves and all three link check jobs pass with BASE_REF and HEAD_REF taken from the inputs. The nightly run on the ciflow/nightly tag, the case that used to exit 128, is skipped.

Not addressed here

lint_urls.sh and lint_xrefs.sh end their input pipeline with || true, needed because git grep exits 1 when it finds nothing. It also means that if git grep fails outright the lint reports zero findings and passes. That is the same swallowing described above, it is pre-existing, and it deserves its own change.

Copilot AI lite review requested due to automatic review settings August 7, 2026 14:08
@pytorch-bot

pytorch-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21644

Note: Links to docs will display an error until the docs builds have been completed.

❌ 9 New Failures, 9 Unclassified Failures

As of commit 71d139a with merge base 812c7f0 (image):

NEW FAILURES - The following jobs have failed:

  • Apple / build-frameworks-ios / macos-job (gh)
    ##[error]fatal: unable to access 'https://github.com/pytorch/cpuinfo.git/': Could not resolve host: github.com
  • pull / test-qnn-testsuite-linux / test-backend-linux (qnn, models) / linux-job (gh)
    API rate limit exceeded
  • Test CUDA Builds / check-all-cuda-builds (gh)
    API rate limit exceeded
  • Test CUDA Builds / export-model-cuda-artifact (SocialLocalMobile, Qwen3.5-35B-A3B-HQQ-INT4, quantized-int4-tile-packed) / linux-job (gh)
    RuntimeError: Command bash /__w/_temp/exec_script failed with exit code 1
  • Test CUDA Builds / test-executorch-cuda-build-12.6 / linux-job (gh)
    ##[error]API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID B0C4:34D21C:1C9D79:6158A3:6A761965 and timestamp 2026-08-07 17:44:05 UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting
  • Test CUDA Builds / test-executorch-cuda-build-13.0 / linux-job (gh)
    ##[error]API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID 819A:4F69D:1AC6BE:5AE847:6A76193B and timestamp 2026-08-07 17:43:23 UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting
  • Test CUDA Builds / test-models-cuda / linux-job (gh)
    ##[error]An error occurred trying to start process '/usr/bin/bash' with working directory '/home/ec2-user/actions-runner/_work/executorch/executorch/pytorch/executorch'. No such file or directory
  • Test CUDA Builds / unittest-cuda / linux-job (gh)
    ##[error]API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID E246:12CB7A:BEDA4:2839F8:6A761969 and timestamp 2026-08-07 17:44:09 UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting
  • Test CUDA Builds / unittest-cuda-runtime / linux-job (gh)
    ##[error]API rate limit exceeded for installation. If you reach out to GitHub Support for help, please include the request ID D1C8:38E2:1CD10A:61D40E:6A761944 and timestamp 2026-08-07 17:43:32 UTC. For more on scraping GitHub and how it may affect your rights, please review our Terms of Service (https://docs.github.com/en/site-policy/github-terms/github-terms-of-service) - https://docs.github.com/en/rest/using-the-rest-api/getting-started-with-the-rest-api#rate-limiting

UNCLASSIFIED FAILURES - DrCI could not classify the following jobs because the workflow did not run on the merge base. The failures may be pre-existing on trunk or introduced by this PR:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 7, 2026

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

_link_check.yml inferred which commits to diff from the event that
triggered its caller. A push creating a tag or a branch reports an all
zero base SHA, so the fetch failed with exit 128 and no linting ran. A
scheduled or manual run has no base at all, and reached a whole tree
scan only because an unquoted command substitution happened to drop
the empty argument.

Pass the base in explicitly. Empty means whole tree, which the lint
scripts already support, and the all zero SHA maps to empty. The fetch
folds into the lint step, so each job is one step shorter than before.

nightly.yml runs the check only on the schedule and on a manual
dispatch. Its pull request runs were checking nothing: it passes the
merge commit as the ref, so the head commit is never fetched and
git diff base..head cannot resolve. lint_urls.sh and lint_xrefs.sh
swallowed that and passed having read zero URLs, while
lint_file_size.sh failed hard on every such run. Pull requests get the
real check from lint.yml, against their own base.
@shoumikhin
shoumikhin force-pushed the link-check-base-ref branch from 7a1237a to 71d139a Compare August 7, 2026 15:05
Copilot AI review requested due to automatic review settings August 7, 2026 15:05

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin merged commit 35c7297 into main Aug 7, 2026
834 of 976 checks passed
@shoumikhin
shoumikhin deleted the link-check-base-ref branch August 7, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/binaries/all Release PRs with this label will build wheels for all python versions ciflow/binaries ciflow/cuda ciflow/nightly ciflow/periodic ciflow/trunk CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: none Do not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants