Skip to content

Harden Action release outputs against stdout-scraping (silent-empty tags/has-changes) #594

Closed

Description

@goosewobbler

Problem

The releasekit Action advertises release-mode outputs — tags, has-changes, version-output, release-output (see action.yml). scripts/run-action.mjs derives them by parsing the CLI's stdout:

function writeReleaseOutputs(input, stdout) {
  const parsed = parseReleaseOutput(stdout, ...);
  const tags = parsed?.versionOutput?.tags;
  setOutput('tags', Array.isArray(tags) ? tags.join(',') : '');   // '' when stdout didn't parse
  setOutput('has-changes', !!parsed?.versionOutput?.updates?.length ? 'true' : 'false');
}

If the CLI's stdout comes back without a parseable .versionOutput — a rare/transient capture hiccup, the same failure mode that stranded the v0.41.1 action dist (#593) — every release output silently degrades to empty / false, with a green check.

A user relying on the advertised contract:

- uses: goosewobbler/releasekit@vX
  id: release
  with: { mode: release }
- if: steps.release.outputs.tags != ''
  run: ./promote "${{ steps.release.outputs.tags }}"

then silently skips their downstream step even though the release actually tagged and published. This is a user-facing footgun on an output contract people build automation on.

Proposed hardening (two tiers)

Tier 1 — git-derive tags + verify (targeted, no CLI change).
In writeReleaseOutputs (and the standing-pr-publish output path), after the release/publish has run in the checkout, source the tag authoritatively from git — git tag --points-at HEAD filtered to version tags — cross-checked against the parsed stdout. If the CLI reported changes but git shows no version tag, warn / set a tag-verification-failed output (optionally exit non-zero under a strict flag). Hardens the single most load-bearing output.

Tier 2 — file-based structured output (comprehensive).
tags can come from git, but version-output / release-output need the CLI's structured result, which today only travels via stdout. Add --output <file> (or a temp file) to release / standing-pr publish; run-action.mjs reads the file instead of capturing stdout. Removes stdout-scraping fragility from all release outputs.

Relationship to #593

#593 fixed the same failure class in releasekit's own release workflow (_release.reusable.yml + the action-dist mechanism). This issue is the product counterpart: the outputs that users consume from the Action share the fragility.

Note (correcting an earlier assumption): releasekit's own publish runs the CLI directly inside _release.reusable.yml — not via the Action — because the OIDC job_workflow_ref must match the npm trusted-publisher entry (one workflow path per package; see the comment at _release.reusable.yml L176-177). So the self-workflow can't just consume steps.releasekit.outputs.tags, and #593's workflow-level git-derivation + fail-loud stay. Tier 2's file output could still let the self-workflow drop its stdout capture later.

Acceptance

  • outputs.tags reflects the real pushed tag(s) even when stdout parsing fails.
  • A release that reports changes but produces no git tag surfaces (warn / output / strict-fail), never silent-empty.
  • (Tier 2) release outputs no longer depend on capturing CLI stdout.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:ciUpdates to Continuous Integrationtype:bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions