Test Self-Referenced Reusable Workflows - #792
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request updates the hub’s reusable workflows and gates to support GitHub Actions’ $/ self-repository reference syntax, so hub-owned composite actions and sibling reusable workflows can be referenced at the workflow’s pinned commit without additional hub checkouts or duplicated job bodies.
Changes:
- Allow
$/self-repository refs (and local refs) in the SHA-pinning gate and its unit tests. - Switch hub reusable workflows from
.hub-checkout-based composite-action resolution to$/references, and nest release/docker/version workflows via$/. - Rename default “package” actions for the technology they run, and remove the obsolete Ninja codegen secret from the codegen task interface.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/tests/test_repo_gate.py | Extends sha-pin tests to treat $/... as a non-pinnable self-reference. |
| scripts/repo_gate.py | Skips sha-pin checks for $/ refs (self-repo) and updates related documentation. |
| scripts/README.md | Updates documented behavior of sha-pin (now also describes self-repo refs). |
| .github/workflows/validate-task.yml | Uses $/ to call the hub default validate action without a .hub checkout fallback. |
| .github/workflows/run-codegen-pull-request-task.yml | Removes the optional Ninja API key secret from the workflow_call interface. |
| .github/workflows/publish-docker-readme-task.yml | Uses $/ to run hub default docker-readme transform action without a .hub checkout. |
| .github/workflows/build-release-task.yml | Nests get-version + docker build via $/ reusable workflows; refactors defaults/inputs and validation. |
| .github/workflows/build-docker-task.yml | Uses $/ for the hub default docker-prepare action without a .hub checkout. |
| .github/actions/pypi-build-default/action.yml | Renames and tightens required inputs (project dir + version file). |
| .github/actions/nuget-push-default/action.yml | Renames and makes project-file required. |
| .github/actions/dotnet-publish-default/action.yml | Renames and makes project-file required; updates step naming. |
| .github/actionlint.yaml | Adds scoped ignores for actionlint not recognizing $/ references. |
Suppressed comments (1)
scripts/repo_gate.py:266
check_sha_pin’s docstring says it checks “uses naming an action”, but the regex scans alluses:(actions and reusable workflows). It also implies every checked SHA “resolves”, but resolution is intentionally limited to refs under the scanned repo’s owner. Updating the docstring would prevent readers from inferring broader guarantees than the function provides.
def check_sha_pin(root: Path, files: list[str]) -> list[str]:
"""Every `uses:` naming an action is a 40-hex SHA, and one under this owner is a SHA that
resolves. A local or self-repository ref names the running commit and is skipped.
Resolution is scoped to the scanned repository's own owner, because that is where the fleet's
own actions live and where the decay this catches comes from: a squash merge deletes the
branch a pin was taken from, and the pin outlives the commit. A third-party action's tag is
stable by comparison, and reading one would make every local run of this gate depend on a
stranger's repository answering. The cost is stated rather than left to be found, and it is
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Suppressed comments (3)
.github/workflows/build-release-task.yml:141
- The validate-release job enforces project-path inputs purely based on enable_* flags, but the workflow still supports caller-provided build-* hooks. Without checking whether a hook is present, this can force repos that use a custom hook (or any existing callers relying on prior defaults) to supply inputs that may be irrelevant to their hook, and it also makes the new required-by-default contract harder to evolve safely. Consider checking for hook presence (after a checkout) and only requiring these inputs when the hub default will actually run.
steps:
- name: Validate branch and version consistency step
env:
SEMVER2: ${{ needs.get-version.outputs.SemVer2 }}
BRANCH: ${{ inputs.branch }}
.github/workflows/build-release-task.yml:156
- The project-input validation should account for whether the caller has provided a build hook. As written, enabling PyPI requires pypi_version_file even when a caller-provided build-pypi hook is used (and that hook path doesn't receive version-file), which can cause unnecessary failures.
if [[ "$ENABLE_EXECUTABLE" == "true" && -z "$EXECUTABLE_PROJECT" ]]; then
echo "::error::inputs.executable_project is required when inputs.enable_executable is true."
exit 1
fi
if [[ "$ENABLE_NUGET" == "true" && -z "$NUGET_PROJECT" ]]; then
scripts/README.md:85
- The sha-pin gate checks all
uses:references in workflow files (step actions and reusable workflows), not only reusable-workflow calls. The README bullet currently says "every external workflowuses:reference", which could be read as excluding step actions likeactions/checkoutthat are also enforced by this check.
- `sha-pin`: every external workflow `uses:` reference is a 40-hex commit SHA that resolves, with the documented `dotnet/nbgv@master` exception. Local (`./`) and self-repository (`$/`) references run at the workflow commit, so they need no separate pin.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (3)
.github/workflows/build-release-task.yml:163
- validate-release currently requires inputs.pypi_version_file whenever enable_pypi is true, but that input is only used by the hub default build-pypi implementation (repos with a custom .github/actions/build-pypi hook won’t consume it). This makes pypi_version_file an unconditional contract even when it’s irrelevant to the selected hook path.
if [[ "$ENABLE_PYPI" == "true" && ( -z "$PYPI_PROJECT_DIR" || -z "$PYPI_VERSION_FILE" ) ]]; then
echo "::error::inputs.pypi_project_dir and inputs.pypi_version_file are required when inputs.enable_pypi is true."
exit 1
fi
.github/workflows/build-release-task.yml:304
- build-release-task.yml previously had an explicit check that DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN were non-empty before attempting Docker auth. After delegating to build-docker-task.yml, that fast-fail with a targeted error message is gone, and build-docker-task.yml does not currently perform an equivalent non-empty validation before docker/login-action runs. This can make missing-secret failures harder to diagnose.
build-docker:
name: Build Docker image job
needs: [get-version, validate-release, build-executable, build-nuget, build-pypi]
if: ${{ inputs.enable_docker && !failure() && !cancelled() }}
uses: $/.github/workflows/build-docker-task.yml
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
with:
.github/workflows/build-release-task.yml:290
- The hub default build-pypi path requires inputs.pypi_version_file, but build-release-task.yml currently only checks it in validate-release (which can’t distinguish between caller hook vs hub default). Add a fast-fail check here scoped to the hub-default path (no .github/actions/build-pypi hook), so custom hooks aren’t forced to provide an unused input.
- name: Run hub build-pypi default step
if: ${{ hashFiles('.github/actions/build-pypi/action.yml') == '' }}
uses: $/.github/actions/pypi-build-default
with:
branch: ${{ inputs.branch }}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
pyproject.toml:15
- [tool.mypy]
excludeis configured as a TOML array, but mypy expects a single regex string. As-is, mypy may ignore the setting or fail to parse it, which would reintroduce the duplicate-module problem this change is trying to avoid.
[tool.mypy]
python_version = "3.13"
files = ["spec", "host-setup", "scripts"]
mypy_path = [".github/actions/prose-gate", ".github/actions/repo-gate"]
exclude = ["scripts/prose_lint.py", "scripts/repo_gate.py"]
scripts/tests/test_repo_gate.py:111
- This test builds a workflow with a job-level
uses:to$/.github/actions/validate-default, but job-leveluses:only supports reusable workflows (not composite actions). The repo gate is regex-based so the test still passes, but using syntactically valid YAML here makes the test more representative and easier to reason about.
def test_a_local_or_self_repository_ref_needs_no_pin(self) -> None:
for ref in (
"./.github/workflows/validate-task.yml",
".github/workflows/validate-task.yml",
"$/.github/workflows/validate-task.yml",
"$/.github/actions/validate-default",
):
with self.subTest(ref=ref):
files = self.workflow(f"jobs:\n a:\n uses: {ref}\n")
self.assertEqual([], repo_gate.check_sha_pin(self.tmp, files))
Uh oh!
There was an error while loading. Please reload this page.
ptr727
commented
Aug 17, 2026
Suppressed review findings answered (9 total across four rounds):
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (4)
.github/workflows/build-release-task.yml:232
- When the caller does not provide a
build-nugethook, the hub default action requiresproject-file, but this workflow does not validateinputs.nuget_projectbefore invoking the default. Adding an explicit check here makes the failure clearly actionable for downstream callers.
- name: Run hub build-nuget default step
if: ${{ hashFiles('.github/actions/build-nuget/action.yml') == '' }}
uses: $/.github/actions/nuget-push-default
.github/workflows/build-release-task.yml:270
- When the caller does not provide a
build-pypihook, the hub default action requires bothproject-dirandversion-file, but this workflow does not validateinputs.pypi_project_dir/inputs.pypi_version_filebefore invoking the default. Adding an explicit check here makes missing/empty inputs fail with a workflow-level message that points to the rightwith:fields.
- name: Run hub build-pypi default step
if: ${{ hashFiles('.github/actions/build-pypi/action.yml') == '' }}
uses: $/.github/actions/pypi-build-default
with:
docs/reusable-workflows.md:185
- The doc says
build-release-task.yml"validates those inputs" (explicit project paths) when targets are enabled, but the current implementation relies on the called default actions to fail when paths are empty (e.g.,dotnet-publish-defaultrequiresproject-file). This is a documentation mismatch that can mislead downstream adopters about where validation occurs.
`build-release-task.yml` reaches `get-version-task.yml` and `build-docker-task.yml` through `$/`, so both sibling tasks resolve at the same hub commit the downstream caller pins. It keeps `validate-release` inline because that gate belongs to the release orchestrator. `build-docker-task.yml` also ships as a task in its own right for a caller that wants only the Docker leg. The `dotnet-publish-default`, `nuget-push-default`, and `pypi-build-default` actions require explicit project paths. `build-release-task.yml` validates those inputs when their targets are enabled and forwards them to the matching default.
.github/workflows/build-release-task.yml:189
- When the caller does not provide a
build-executablehook, the hub default action requiresproject-file, but this workflow does not validateinputs.executable_projectbefore invoking the default. The resulting failure message comes from the action and doesn’t clearly point back to the workflow input that needs to be set.
This issue also appears in the following locations of the same file:
- line 230
- line 267
- name: Run hub build-executable default step
if: ${{ hashFiles('.github/actions/build-executable/action.yml') == '' }}
uses: $/.github/actions/dotnet-publish-default
Uh oh!
There was an error while loading. Please reload this page.
ptr727
commented
Aug 17, 2026
Late suppressed findings from review 4953201774, which arrived after the earlier consolidated disposition:
All four suppressed findings now have an explicit disposition. |
## Summary Correct the reusable-workflow guide's description of default-action input validation. ## Why The late suppressed review on #792 correctly identified that the guide assigned validation to `build-release-task.yml`. Each selected hub default action performs that validation instead. Caller-provided hooks remain free to use different inputs. ## Validation - Diff-scoped prose lint - markdownlint, 120 files - Ruff lint and format checks - mypy - Repository gate - EditorConfig checker - `git diff --check`
What Changed
$/self-repository syntax.Why
This draft tests whether a cross-repository reusable workflow can resolve sibling workflows and actions at its own pinned commit. A successful PhotoCleaner run removes the need for extra hub checkouts and synchronized workflow copies.
Validation
PhotoCleaner pull request #58 proves the cross-repository behavior in Actions run 32047594855.