Skip to content

fix(release): heal package-lock on partial semantic-release failure#1774

Open
rainer-friederich wants to merge 1 commit into
mainfrom
fix/release-partial-sr-selfheal
Open

fix(release): heal package-lock on partial semantic-release failure#1774
rainer-friederich wants to merge 1 commit into
mainfrom
fix/release-partial-sr-selfheal

Conversation

@rainer-friederich

Copy link
Copy Markdown
Contributor

1. Abstract

Makes the Release job's Sync package-lock.json after release self-heal fire on a partial Semantic Release failure, not only on a fully green release, so a release that publishes one package and then fails on a later one no longer leaves main's root package-lock.json drifted. Also extends RELEASE-RUNBOOK Failure mode 7 with this variant and its root cause.

2. Reasoning

The 4.0.0 release of spacecat-shared-data-access published and pushed its chore(release) commit, then the same run failed later at spacecat-shared-ticket-client (404 - package not found on the OIDC exchange → ENONPMTOKEN). Because the sync step was gated if: success(), the upstream Semantic Release failure short-circuited it — the step was skipped, not run. So main was left pinning data-access@3.81.0 in the lock while package.json said 4.0.0, and every open PR went red at Verify package-lock.json is in sync with a diff it did not author. The single scenario that most needs the re-sync (a release commit was pushed, then SR failed) was exactly the one the success() gate missed.

3. High-level overview of the changes

Behaviour delta in .github/workflows/main.yaml (Release job, sync step):

  • Gate if: success()if: success() || failure() (keeps continue-on-error: true). A partial Semantic Release failure now still triggers the lockfile re-sync. Cancellation stays excluded (not always()), so a job cancelled mid-push cannot race in a commit.
  • New HEAD-is-release-commit guard. Before regenerating, the step checks that HEAD is a chore(release): … commit (the subject semantic-release pushes). On the failure() path this ensures it only commits when a release commit was actually pushed this run; on a failure before any release was committed (e.g. npm ci failed) HEAD is the triggering feat/fix commit, so the step no-ops instead of committing a spurious lock diff. The existing git diff --quiet drift check is unchanged.

Net effect across cases: clean-release-with-drift still heals (unchanged); the partial-failure case from the linked issue now heals (the fix); every other case (nothing released, early failure, cancellation) safely no-ops or is skipped.

Docs (docs/RELEASE-RUNBOOK.md, Failure mode 7): documents the success() || failure() gate + HEAD guard, and adds an "Underlying cause" subsection describing the real trigger — a newly-added publishable package with no npm trusted-publisher binding (OIDC 404ENONPMTOKEN), noting the accompanying reading 'map' TypeError is secondary noise, and pointing to Failure mode 1 for the binding bootstrap.

4. Required information

6. Additional information outside the code

  • Traced the failing release run 28669792110 (Actions logs): confirmed data-access@4.0.0 published + pushed its release commit at 15:45, then the -ws loop failed at spacecat-shared-ticket-client's verifyConditions at 15:46 with OIDC token exchange failed: 404 - package not foundENONPMTOKEN No npm token specified. The TypeError: ... reading 'map' originates in semantic-release-monorepo/src/only-package-commits.js:26 and is a secondary crash, not the cause.
  • Confirmed the root cause is live and ongoing: npm view @adobe/spacecat-shared-ticket-client returns E404 (never published), and it is absent from PACKAGES in scripts/setup-npm-trusted-publishers.sh. The bootstrap of that binding is deliberately out of scope for this PR (tracked separately); until it is done, releases keep half-failing — but with this fix main's lock auto-heals each time so PRs are no longer taxed.
  • Validated the edited workflow: the YAML parses and the step's gate resolves to success() || failure(); the embedded shell (HEAD guard + regen) was checked against the full scenario matrix (clean / no-drift / nothing-released / partial-failure / early-failure / cancelled).

7. Test plan

  • Local: the Release job runs only on main, so it cannot be exercised on a branch. Verified statically instead — parsed the workflow, confirmed the resolved gate, and walked the step's guard logic through each release outcome (see section 6).
  • prod (main, next release): on the next merge that releases a package, confirm the Sync package-lock.json after release step runs. To exercise the fix specifically, after any partial Semantic Release failure that already pushed a chore(release) commit, confirm the step runs (green ✓) and pushes a chore(release): sync package-lock.json after release [skip ci] commit, and that subsequent unrelated PRs stay green at Verify package-lock.json is in sync (i.e. no manual FM-7 lockfile PR needed).

🤖 Generated with Claude Code

The Release job's "Sync package-lock.json after release" step was gated
`if: success()`, so when semantic-release published + pushed a package's
release commit and then failed later in the same `-ws` run (e.g. a
subsequent workspace whose npm OIDC exchange 404s — a new, not-yet-published
package with no trusted-publisher binding), the heal was SKIPPED. main was
left with a drifted root package-lock.json and every open PR went red at
`Verify package-lock.json is in sync` — the exact scenario that most needs
the re-sync.

- Change the gate to `if: success() || failure()` (keep continue-on-error)
  so a partial SR failure still triggers the lock re-sync. Cancellation is
  excluded so a job cancelled mid-push cannot race in a commit.
- Add a HEAD-is-release-commit guard so the failure() path only commits when
  a `chore(release): ...` commit was actually pushed this run; on an early
  failure (e.g. npm ci) it no-ops instead of committing a spurious lock diff.
- Extend RELEASE-RUNBOOK Failure mode 7 with the partial-SR-failure variant,
  the HEAD guard, and the root cause (new publishable package without an npm
  trusted-publisher binding; the `reading 'map'` TypeError is secondary
  noise). Real example: data-access@4.0.0 published, then the run failed at
  ticket-client (#1701, never published, absent from setup PACKAGES).

Fixes #1773

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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