From a343f553034026ac5049a79a63058813783fd7f7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 10:33:14 +0000 Subject: [PATCH] Reference the pipelines absolutely, not relatively Sorting's first push to main after adopting ci.yml failed to compile: error parsing called workflow ".github/workflows/ci.yml" -> "ktsu-dev/.github/.github/workflows/ci-shared.yml@release" (source tag with sha:af44498...) --> "./.github/workflows/dotnet.yml" : workflow was not found. af44498 is the annotated tag's own object sha, not c9ef0b3 which it points at. A relative `./` inside a workflow reached through a tag is resolved against the tag object, and a tag object has no tree, so nothing is found. This is worth stating precisely because the relative form DID resolve on the canary's pull_request run -- the run recorded dotnet.yml@af44498 and went green across all eight jobs -- and I took that as proof the nested case worked. It holds on pull_request and fails on push, so the evidence was real and the conclusion drawn from it was wrong. A form that works until the first push to a default branch is worse than one that never works. Naming the repository and tag explicitly resolves identically on every event. `release` is promoted as one unit, so the dispatcher and the pipeline it selects still move together. The cost is that a pull request here tests its ci-shared.yml against the released pipelines rather than its own, so changing a pipeline and the dispatcher together wants two promotions. Recorded in the docs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014RABe2NufFc9hwm94iB3Rf --- .github/workflows/ci-shared.yml | 22 ++++++++++++++++------ docs/shared-ci.md | 25 ++++++++++++++++++++----- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci-shared.yml b/.github/workflows/ci-shared.yml index 223a394..36605c4 100644 --- a/.github/workflows/ci-shared.yml +++ b/.github/workflows/ci-shared.yml @@ -106,11 +106,21 @@ jobs: name: .NET needs: detect if: needs.detect.outputs.stack == 'dotnet' && needs.detect.outputs.private == 'false' - # Relative, so the pipeline is resolved from the same commit of this repository as - # this file. That keeps ci-shared.yml and the pipeline it selects versioned together: - # moving the `release` tag moves both atomically, and a pull request against this - # repository tests its own pipeline rather than the released one. - uses: ./.github/workflows/dotnet.yml + # Absolute rather than relative, and the reason is not style. A relative `./` inside a + # workflow that was itself reached through a tag is resolved by GitHub against the tag + # OBJECT rather than the commit it points at, and a tag object has no tree, so the + # lookup finds nothing: + # + # error parsing called workflow ".github/workflows/ci.yml" + # -> "ktsu-dev/.github/.github/workflows/ci-shared.yml@release" (source tag with sha:af44498...) + # --> "./.github/workflows/dotnet.yml" : workflow was not found. + # + # That is the annotated tag's own sha, not c9ef0b3 which it points to. The relative + # form did resolve on a pull_request event, which is what made this look settled; it + # fails on push. Naming the repository and the tag explicitly resolves identically on + # every event. `release` is promoted as one unit, so ci-shared.yml and the pipeline it + # selects still move together. + uses: ktsu-dev/.github/.github/workflows/dotnet.yml@release secrets: inherit with: version-bump: ${{ inputs.version-bump }} @@ -119,7 +129,7 @@ jobs: name: .NET (private) needs: detect if: needs.detect.outputs.stack == 'dotnet' && needs.detect.outputs.private == 'true' - uses: ./.github/workflows/dotnet-private.yml + uses: ktsu-dev/.github/.github/workflows/dotnet-private.yml@release secrets: inherit with: version-bump: ${{ inputs.version-bump }} diff --git a/docs/shared-ci.md b/docs/shared-ci.md index a0a725b..50d9ec2 100644 --- a/docs/shared-ci.md +++ b/docs/shared-ci.md @@ -116,11 +116,26 @@ cancelled, so a run that may already have moved the tag is never interrupted. Moving the tag by hand works too, but skips all of the above. -Inside `ci-shared.yml` the pipelines are referenced relatively (`./.github/workflows/...`), -which resolves to the same commit of this repository as `ci-shared.yml` itself. So the -dispatcher and the pipeline it selects are always versioned together: moving `release` -moves both atomically, and a pull request here tests its own pipelines rather than the -released ones. +Inside `ci-shared.yml` the pipelines are referenced **absolutely**, at `@release` — not +relatively. A relative `./` inside a workflow that was itself reached through a tag is +resolved by GitHub against the tag *object* rather than the commit it points at, and a tag +object has no tree, so the lookup fails: + +```text +error parsing called workflow ".github/workflows/ci.yml" + -> "ktsu-dev/.github/.github/workflows/ci-shared.yml@release" (source tag with sha:af44498...) + --> "./.github/workflows/dotnet.yml" : workflow was not found. +``` + +That sha is the annotated tag's own, not the commit it points to. The relative form does +resolve on a `pull_request` event, which is exactly what makes this trap worth writing +down — it looks correct until the first push to a default branch. The absolute form +resolves identically on every event, and because `release` is promoted as one unit the +dispatcher and the pipeline it selects still move together. + +The cost is that a pull request against this repository tests its own `ci-shared.yml` +against the *released* pipelines rather than its own. Changing a pipeline and the +dispatcher together therefore wants two promotions, or a throwaway tag. ## Interaction with the Dependabot merge gate