Skip to content

fix(upgrade): restrict delta patch from-version to same major.minor series - #1329

Merged
BYK merged 2 commits into
mainfrom
fix/prev-tag-same-series
Aug 1, 2026
Merged

BYK merged 2 commits into
mainfrom
fix/prev-tag-same-series

Conversation

@BYK

@BYK BYK commented Jul 31, 2026

Copy link
Copy Markdown
Member

Problem

generate-patches was picking the wrong PREV_TAG for 0.40 nightly
builds, causing patches to be generated from a 0.41 source and stamped
with from-version: 0.41.x-dev.Y — useless for any user on the 0.40
series. Their chain resolution then fails (their version is
chronologically before 0.41) and the upgrade falls back to full-download
every time.

Repro on the live registry

ghcr.io/getsentry/cli:patch-0.40.0-dev.1785524715
  from-version: 0.41.0-dev.1785504113   ← should be 0.40.0-dev.1785522378

ghcr.io/getsentry/cli:patch-0.40.0-dev.1785526951
  from-version: 0.41.0-dev.1785504113   ← should be 0.40.0-dev.1785524715

Root cause

The PREV_TAG loop in generate-patches:

TAGS=$(oras repo tags "${REPO}" 2>/dev/null | grep '^nightly-[0-9]' | sort -V || echo "")
for tag in $TAGS; do
  if [ "$tag" = "nightly-${VERSION}" ]; then break; fi
  PREV_TAG="$tag"
done

sort -V orders 0.41.0-dev.X after every 0.40.0-dev.Y (because
0.41 > 0.40 lexicographically). At the time generate-patches runs,
the current build's tag hasn't been pushed yet — so the
if [ "$tag" = "nightly-${VERSION}" ] break never triggers. The loop
walks past every 0.40 tag and onto the 0.41 tag, and PREV_TAG becomes
the 0.41 nightly.

Fix

Filter TAGS to only those in the same major.minor series as the
current build before iterating. MAJOR_MINOR is parsed from VERSION
(echo "${VERSION}" | cut -d. -f1,2 → e.g. 0.40). For 0.40 builds
the loop only sees nightly-0.40.0-dev.* tags; for 0.41 builds only
nightly-0.41.0-dev.*. The first build of a new series (no prior tags
in the same series) falls through with PREV_TAG="" and the existing
HAS_PREV skip handles it cleanly.

Retroactive remediation (follow-up)

The two mis-annotated patches above have correct bytes but wrong
annotations. Re-stamping them with the correct from-version will let
existing 0.40-series users chain through them to the latest. Tracked as
a separate follow-up — they're already on the registry, just need the
annotation re-pushed.

Relationship to PR #1327

PR #1327 prevents annotation drift between generate-patches and
publish-nightly (they now agree via job outputs). This fix prevents
the initial PREV_TAG selection from being wrong in the first place.
Together they make the CI pipeline produce correct annotations
end-to-end.

Verification

  • Local simulation: MAJOR_MINOR filter + iteration over same-series
    tags returns nightly-0.40.0-dev.1785522378 for VERSION=0.40.0-dev.1785524715
    vs. the current broken nightly-0.41.0-dev.1785504113.
  • Workflow YAML parses cleanly.
  • Will re-verify against a real build after merge (next nightly).

…eries

The PREV_TAG loop in `generate-patches` was walking past all 0.40
nightly tags and onto the lone 0.41.0-dev.1785504113 tag because
`sort -V` orders 0.41 > 0.40 lexicographically. At the time
generate-patches runs, the current build's tag hasn't been pushed
yet, so the loop's `if [ "$tag" = "nightly-${VERSION}" ]` break
never triggers — the loop ends naturally after iterating every
existing tag, and PREV_TAG becomes the 0.41 nightly.

Result: patches were generated FROM the 0.41 binary but stamped
with `from-version: 0.41.x-dev.Y` in the annotation. chain
resolution then fails for any user on the 0.40 series (their
current version is chronologically *before* 0.41.0-dev.1785504113
and the patch's from-version annotation doesn't match any chain
step), so delta upgrades fall back to full-download — every time.

Repro on the live registry:

```
# Inspect recent patches
$ ghcr.io/getsentry/cli:patch-0.40.0-dev.1785524715
  from-version: 0.41.0-dev.1785504113   ← WRONG (should be 0.40.0-dev.1785522378)
$ ghcr.io/getsentry/cli:patch-0.40.0-dev.1785526951
  from-version: 0.41.0-dev.1785504113   ← WRONG (should be 0.40.0-dev.1785524715)
```

Fix: filter TAGS to only those in the same major.minor series as
the current build before iterating. `MAJOR_MINOR` is parsed from
VERSION (`echo "${VERSION}" | cut -d. -f1,2` → e.g. `0.40`).
For `0.40.0-dev.X` builds the loop only sees `nightly-0.40.x-dev.*`
tags; for `0.41.0-dev.X` only `nightly-0.41.x-dev.*`. The first
build of a new series (no prior tags in the same series) falls
through with PREV_TAG="" and the existing HAS_PREV skip handles
it cleanly.

Retroactive remediation: the two mis-annotated patches above will
need their `from-version` annotations re-stamped (their patch bytes
are already correct — only the annotation is wrong). A separate
follow-up will re-publish `patch-0.40.0-dev.1785524715` with
`from-version=0.40.0-dev.1785522378` and
`patch-0.40.0-dev.1785526951` with
`from-version=0.40.0-dev.1785524715` so existing users on the
0.40 series can chain through them.

PR #1327 already prevents annotation drift between generate-patches
and publish-nightly — this fix prevents the initial PREV_TAG selection
from being wrong in the first place.
Comment thread .github/workflows/ci.yml
Adversarial review (NIT-2): the dot in `0.40` was being interpolated
into a regex `grep` pattern, where `.` matches any character. In
practice this can't mis-match because `oras repo tags` is filtered
to `^nightly-[0-9]` upstream and `MAJOR_MINOR` is always digits.digits
— but `grep -F` (fixed-string) makes the intent explicit and matches
the defensive style of the rest of the script.
@BYK
BYK enabled auto-merge (squash) July 31, 2026 23:57
@BYK
BYK merged commit 255f8a8 into main Aug 1, 2026
44 of 46 checks passed
@BYK
BYK deleted the fix/prev-tag-same-series branch August 1, 2026 00:00
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-01 00:00 UTC

BYK added a commit to BYK/binpatch that referenced this pull request Aug 1, 2026
Closes #40. The `publish-ghcr` mode re-derived the previous version from
the registry independently of the `generate-ghcr` step that actually
downloaded the source binary and ran bsdiff. If the registry state
changed between the two steps, the two values could disagree and
`binpatch` consumers would silently hit SHA mismatch (the library only
verifies the final output SHA, not the source binary identity — the
annotation is a chain pointer, not a content hash).

## Fix

Add a new `from-version` input on `publish-ghcr`. When set, the
annotation on the pushed patch manifest uses THIS value verbatim. When
empty (back-compat), the old registry re-derive runs and prints a hint
suggesting the caller thread the value from `generate-ghcr`.

## Consumer wiring

```yaml
- uses: BYK/binpatch/action@vX.Y.Z
  id: gen
  with: { mode: generate-ghcr, ... }

- uses: BYK/binpatch/action@vX.Y.Z
  with:
    mode: publish-ghcr
    from-version: ${{ steps.gen.outputs.from-version }}
```

## Files

- action/action.yml: add `from-version` input, thread to push_patches
via env, use input when set else re-derive
- action/README.md: document the new input and the gen→publish wiring

## Relationship to other fixes

- #37 — fd-close fix in applyReaderToFile (released as
0.4.0)
- getsentry/cli#1329 — same-series filter in custom workflow (now
merged)
- getsentry/cli#1327 — CI workflow annotation threading (now merged)

Combined, the chain-side fixes (binpatch fd-close + getsentry/cli CI
workflow) and this action-side fix mean the consumer can stop running
its own hand-rolled patch generation/publish and consume the binpatch
action directly.

## Verification

- pnpm test: 58 passed
- pnpm build: clean
- pnpm typecheck: clean
- pnpm lint: (no script)
BYK added a commit to BYK/binpatch that referenced this pull request Aug 1, 2026
…44)

The `prev-ghcr` step walked past all 0.40 nightly tags and onto a stray
0.41 nightly because `sort -V` orders 0.41 > 0.40 lexicographically. At
the time this step runs, the current build's own tag hasn't been pushed
yet, so the loop's `if [ "$tag" = ... ]` break never triggers — the loop
ends naturally after iterating every existing tag, and PREV_TAG becomes
the 0.41 nightly.

Result: patches were generated FROM the 0.41 binary and stamped with
`from-version: 0.41.x-dev.Y` — useless to any user on the 0.40 series.

## Fix

Filter TAGS to only those in the same major.minor series as the current
build before iterating. `MAJOR_MINOR` is parsed from VERSION (`echo
"${VERSION}" | cut -d. -f1,2` → e.g. `0.40`). For `0.40.0-dev.X` builds
the loop only sees `nightly-0.40.x-dev.*` tags; for `0.41.0-dev.X` only
`nightly-0.41.x-dev.*`. First build of a new series falls through with
PREV_TAG="" and the existing skip-empty-PREV path handles it cleanly.

Also uses `grep -F` (fixed-string) so the dot in MAJOR_MINOR is treated
literally rather than as a regex metacharacter.

## Relationship to getsentry/cli#1329

This is the action-side port of getsentry/cli#1329's hand-rolled
`MAJOR_MINOR` filter. getsentry/cli is migrating to use this action (PR
#1330) so this fix lands ahead of the migration completing.

## Verification

- pnpm test: 58 passed
- YAML parses cleanly
BYK added a commit that referenced this pull request Aug 1, 2026
BYK/binpatch#44 ported the same-series filter from #1329
into the binpatch action's prev-ghcr step, so consumers adopting
`generate-ghcr` no longer need to re-implement this safeguard.

This bumps both the runtime dep (`binpatch` ^0.4.1 → ^0.4.2) and
the action ref (BYK/binpatch/action@0.4.1 → @0.4.2) so the
migration actually picks up the action's own fix.
BYK added a commit that referenced this pull request Aug 1, 2026
…lish (#1330)

Replaces the hand-rolled ~250 lines of `generate-patches` and
`publish-nightly` bash in `.github/workflows/ci.yml` with the
`BYK/binpatch/action@v0.4.1` composite action. The action handles:

- ORAS CLI install + GHCR login
- Previous-nightly discovery (with the same-series filter that fixes the
cross-major nightly tag confusion)
- zig-bsdiff invocation + patch size gate (50% ratio)
- Binary + versioned-tag + patch manifest push with `from-version`
annotation threaded via action outputs

This eliminates the divergence bug we hit twice:

- The PREV_TAG recomputation divergence between generate-patches and
publish-nightly (PR #1327 fixed this in the hand-rolled shell; the
action solves it generically by exposing
`generate-ghcr.outputs.from-version` and consuming it as a
`publish-ghcr` input — see BYK/binpatch#40)
- The cross-major nightly tag confusion (PR #1329 fixed this in the
hand-rolled shell; the action's `validateChainStep` does the equivalent
check at chain-resolution time)

Also bumps the `binpatch` runtime dep from `^0.3.1` to `^0.4.1` for the
new `openSync`/`writeSync`/`closeSync` output path (which avoids the
ETXTBSY race when chaining into a `spawn(tempPath)`).

## Files

- `.github/workflows/ci.yml`: -220 lines of hand-rolled shell, +10 lines
of action wiring
- `packages/cli/package.json`: binpatch ^0.3.1 → ^0.4.1
- `pnpm-lock.yaml`: regenerated

## Diff

```
.github/workflows/ci.yml                           | 347 +++++++-------
packages/cli/package.json                          |   2 +-
pnpm-lock.yaml                                     |  10 +-
```

## Verification

- `node -e 'require("yaml").parse(...)'` parses cleanly
- `pnpm --filter sentry run typecheck`: clean
- `pnpm --filter sentry run lint`: clean
- Lint passes (no shell-script lint script; YAML is the only surface)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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.

1 participant