Skip to content

Deploy: take one tag when HEAD has several, and quote the versions - #2430

Open
tempoloss wants to merge 4 commits into
duckdb:mainfrom
tempoloss:deploy-multiple-tags
Open

Deploy: take one tag when HEAD has several, and quote the versions#2430
tempoloss wants to merge 4 commits into
duckdb:mainfrom
tempoloss:deploy-multiple-tags

Conversation

@tempoloss

@tempoloss tempoloss commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

git tag --points-at HEAD returns every tag on the commit, one per line. Unquoted, that expands to multiple words and shifts every argument after it, so deploy.sh receives the wrong arch and the wrong bucket rather than failing loudly.

It fires today

When this PR was opened I could not find a commit carrying two tags and called it latent. That was a sampling problem, not the state of the registry.

duckdb_mcp's current descriptor ref (7db1650a, teaguesterling/duckdb_mcp) carries both v2.1.2 and v2.2.0. EXT_VERSION is computed in the extension's own checkout - the Deploy job checks out inputs.repository at inputs.ref into the workspace root, then runs git tag --points-at HEAD there - so the next rebuild of that extension runs the unquoted expansion. The deploy step's shell as written, on that tag pair:

main today:   name=duckdb_mcp ext_version=v2.1.2 duckdb_version=v2.2.0 arch=v1.5.5
              bucket=linux_amd64 latest=duckdb-community-extensions versioned=true   argc=8

this PR:      name=duckdb_mcp ext_version=v2.2.0 duckdb_version=v1.5.5 arch=linux_amd64
              bucket=duckdb-community-extensions latest=true versioned=false          argc=7

Eight arguments instead of seven, and BUCKET_NAME lands in the slot the arch belongs to. The tag this PR picks, v2.2.0, is the version the descriptor pins.

Scanning every repo in the registry - 319 extensions, tags resolved through the API so annotated tags report the commit they point at - finds ten commits in this state:

duckdb_mcp        teaguesterling/duckdb_mcp     7db1650a  v2.1.2 v2.2.0   <- a current descriptor ref
anofox_forecast   DataZooDE/anofox-forecast     b04b8173  v0.2.3 v0.2.3-cpp
anofox_tabular    DataZooDE/anofox-tabular      faf35e31  py-v2026.06.17 v2026.06.17
anofox_tabular    DataZooDE/anofox-tabular      ba27f097  py-v0.5.0 v0.5.0
anofox_tabular    DataZooDE/anofox-tabular      0b541d69  py-v0.4.0 v0.4.0
harbor            shreeve/duckdb-harbor         591accf8  duckdb-v2.0.0-alpha37626 v0.8.1
httpd_log         saygox/duckdb-httpd-log       0cfb382a  v0.0.1 v0.0.2
nsv               nsv-format/nsv-duckdb         72f056af  v0.1.0 v0.1.0-rc2
rdf               nonodename/duck_rdf           2d8f309e  v.2.8.0 v2.8.0
sitting_duck      teaguesterling/sitting_duck   adaaf499  v0.9.0 v1.0.0

Nine of the ten are not currently pinned, so they are one descriptor bump away rather than firing. The first one is pinned now.

Four changes

Quote the expansions. This is the fix for the shift. "$EXT_VERSION", "$DUCKDB_VERSION" and "$BUCKET_NAME" keep one value in one argument. Quoting alone is not enough: it turns a shifted argument list into a version string with a newline in the middle of it, which becomes the S3 key.

--sort=-v:refname | head -n 1. Takes a single tag, and version-sorts rather than sorts lexically, so v1.5.10 beats v1.5.9 instead of losing to it.

Prefer a plain release tag. Version sort ranks anything suffixed above the release it belongs to, so head -n 1 would take v1.2.3-rc1 over v1.2.3. Naming the pre-release suffixes fixes the cases somebody thought to name, and cannot be complete: anofox_forecast carries v0.2.3 beside v0.2.3-cpp, and -cpp is not a pre-release anybody would have listed. Matching ^v?[0-9]+([.][0-9]+)*$ first takes the release whatever the variant is called, and falls through to the sorted list when the commit carries no plain tag at all.

The filter is awk and not grep on purpose. This step declares shell: bash, which the runner invokes with -e -o pipefail, and grep exits 1 when nothing matches. Today 220 of the 319 pinned refs carry no tag at all and one more carries quackfix-0.0.3, so for 221 extensions the filter matches nothing on every deploy. With grep that is survivable only because the line begins with export, which swallows the status - as a bare assignment the same pipeline aborts the step:

bash -e -o pipefail -c 'X=$(printf abc | grep -E "^[0-9]+$" | head -n 1); echo reached'  -> exit 1, nothing printed
bash -e -o pipefail -c 'X=$(printf abc | awk  "/^[0-9]+$/"   | head -n 1); echo reached'  -> reached, exit 0

awk exits 0 whether or not it matched, so the filter cannot fail a deploy no matter how the line is later rewritten.

versionsort.suffix. Still there, because it is what decides between v1.0.0-rc1 and v1.0.0-rc2 when there is no plain tag to prefer.

Every pair above, plus the two cases that must not change, through the three lines as they now stand:

[v0.2.3 v0.2.3-cpp]                -> v0.2.3        (main today, and the earlier
[v2.1.2 v2.2.0]                    -> v2.2.0         version of this PR, took -cpp)
[v0.1.0 v0.1.0-rc2]                -> v0.1.0
[py-v0.5.0 v0.5.0]                 -> v0.5.0
[duckdb-v2.0.0-alpha37626 v0.8.1]  -> v0.8.1
[v.2.8.0 v2.8.0]                   -> v2.8.0
[v1.0.0-rc1 v1.0.0-rc2]            -> v1.0.0-rc2    (no plain tag: unchanged)
[v1.5.4]                           -> v1.5.4        (one tag: unchanged)
[]                                 -> a6b8ca0       (no tags: fallback intact)

Both call sites are changed, EXT_VERSION and the DUCKDB_VERSION branch that reads duckdb/duckdb. duckdb/duckdb has 64 tags on 64 distinct commits, so no commit there carries two and that half stays precautionary.

Checked the other way as well: taking the tags that sit on the commit each of the 319 descriptors pins, there are 57 distinct tag sets, and the rule in this PR returns exactly what --sort=-v:refname | head -n 1 returns for all 57. The only row where anything differs from main is the two-tag one. 220 of those refs carry no tag at all and fall through to the commit hash, which is unchanged.

Honestly

${VAR:=...} also became ${VAR:-...}; both work where the result is being assigned anyway, but substitution is what is meant here.

A commit that carries no tag at all still deploys a short commit hash as its version. That is pre-existing and this PR does not change it, though it is visible in the registry right now: the published duckdb_mcp binary carries faf20bd in its extension_version metadata field. I have not traced which deploy produced that, so I am not claiming this bug caused it.

Workflow-only changes generate no checks in this repo, so none of this ran here. It did run on an identical runner: https://github.com/tempoloss/quackiso/actions/runs/32509299741 fetches these three lines from this branch by URL rather than retyping them, then executes them on ubuntu-24.04 under shell: bash, which reports pipefail on and -e in force, with GNU Awk 5.2.1. It asserts the expected tag for all fifteen shapes above, clones teaguesterling/duckdb_mcp at 7db1650a1b4c04914ae724dd43244e690094ffc0 and prints what deploy.sh receives from the real commit:

tags on this commit:
  v2.1.2
  v2.2.0
main today:  argc=8 ext_version=v2.1.2 duckdb_version=v2.2.0 arch=v1.5.5 bucket=linux_amd64
with the PR: argc=7 ext_version=v2.2.0 duckdb_version=v1.5.5 arch=linux_amd64 bucket=duckdb-community-extensions

What that run cannot cover is the deploy itself, which needs this repository's secrets.

@tempoloss tempoloss closed this Aug 3, 2026
@tempoloss
tempoloss deleted the deploy-multiple-tags branch August 3, 2026 13:44
@tempoloss
tempoloss restored the deploy-multiple-tags branch August 3, 2026 13:45
@tempoloss tempoloss reopened this Aug 3, 2026
head -n 1 over -v:refname picks the wrong tag in exactly the case this PR is
about: git ranks v1.2.3-rc1 above v1.2.3, so a commit carrying a release and its
release candidate would deploy the candidate.

One --add per suffix is deliberate. Naming only -rc leaves -beta and -alpha still
sorting above the release, which is worth stating because the single-suffix form
looks sufficient and is not.
szarnyasg pushed a commit that referenced this pull request Aug 5, 2026
The MSVC leg fails to link against the CLAPACK that vcpkg provides, and because
deploy needs the whole build matrix, that one leg took the four platforms that do
build down with it. Nothing was published for DuckDB 1.5.4 on any platform.

Requested by JAicewizard in #2430 while the MSVC LAPACK link is investigated.
@szarnyasg
szarnyasg requested a review from carlopi August 5, 2026 12:48
@tempoloss

Copy link
Copy Markdown
Contributor Author

@samansmink addressing you rather than the descriptor reviewers, since this touches the deploy path.

Two things changed since I opened it, and both make it smaller than it looks.

It is no longer latent. duckdb_mcp's current descriptor ref (7db1650a) carries both v2.1.2 and v2.2.0, so the next rebuild of that extension runs the unquoted expansion: deploy.sh receives 8 arguments instead of 7, with linux_amd64 landing in the bucket slot. Scanning all 319 registry repos finds ten commits in that state, and that one is pinned right now.

The filter is awk and not grep for a reason worth knowing even if you close this: the step declares shell: bash, the runner adds -e -o pipefail, and grep exits 1 when nothing matches. 221 of the 319 pinned refs have no plain release tag for it to match, so with grep the step survives only because the line begins with export, which swallows the status. As a bare assignment the same pipeline ends the job.

Nothing here changes a single-tag commit. Taking the tags that sit on the commit each of the 319 descriptors pins, there are 57 distinct tag sets, and this PR returns what the current code returns for 56 of them; the 57th is the two-tag one. Run on ubuntu-24.04 under the same shell flags, against a real duckdb_mcp checkout rather than a synthetic repo: https://github.com/tempoloss/quackiso/actions/runs/32509299741

If you would rather keep the change to one line, I will drop everything except the quoting and the single-tag selection.

@tempoloss

Copy link
Copy Markdown
Contributor Author

@samansmink ?

@sebastiaan-dev

Copy link
Copy Markdown
Member

Hi @tempoloss, I'll be taking a look today together with @lghavami.

@tempoloss

Copy link
Copy Markdown
Contributor Author

Hi @sebastiaan-dev ! How's it going?

@lghavami

lghavami commented Sep 9, 2026

Copy link
Copy Markdown
Member

Hey @temoploss, thanks for the contribution (+ patience)! I added a few small comments, but otherwise it should be good to go.

# awk and not grep: grep exits 1 when nothing matches, and under this
# step's `-e -o pipefail` that status would abort the deploy for every
# commit whose tags are not plain releases. It survives today only
# because the line begins with `export`, which swallows the status.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, you're configuring the ordering of -rc, -beta, and -alpha, however the awk filter on line 154 strips out anything with a suffix before sorting. Therefore either the three git config calls should be deleted, or the awk filter changed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I see that the fallback does go through the sorted list of tags, so it actually shouldn't be an issue, but could you confirm the intention? Thanks!

run: |
pwd
python3 -m pip install awscli
# A release and its variants can sit on one commit, and version sort

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the big comment, but for code cleanliness its nice to have a more concise explanation of what is being done

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.

3 participants