pulumi/pulumi · error

invalid version %s

Error message

invalid version %s

What it means

When a plugin version is a pre-release, this code expects exactly one pre-release component which encodes a git revision: the actual hash prefixed with an 'x' character (because semver pre-release strings cannot start with 0). This error is thrown when a pre-release version has zero or more-than-one pre-release segments, so no revision hash can be extracted.

Source

Thrown at sdk/go/common/workspace/plugins.go:408

	return gitutil.GetLatestTagOrHash(ctx, source.url)
}

// Downloads a plugin from a git repository.  If the version is a pre-release version, the version is expected to be
// a commit hash, that will be checked out.  Otherwise, the version is expected to be a tag, that will be checked out.
// The tag is expected to be prefixed with a 'v' character.
// If the version is the special sentinel version 0.0.0, we'll use the latest commit on the default branch.
func (source *gitSource) Download(
	ctx context.Context, version semver.Version, _ string, _ string,
	_ func(*http.Request) (io.ReadCloser, int64, error),
) (io.ReadCloser, int64, error) {
	tmpdir, err := os.MkdirTemp("", "pulumi-plugin")
	if err != nil {
		return nil, -1, err
	}
	defer os.RemoveAll(tmpdir)
	if isPreReleaseVersion(version) {
		if len(version.Pre) != 1 {
			return nil, -1, fmt.Errorf("invalid version %s", version)
		}
		// The version string is prefixed with a 'x' character because Pre-versions can't
		// start with a 0. Strip that off to get the actual hash.  Note that we allow short
		// hashes, so we need to create a go-git revision that's then resolved to a full hash.
		revision := plumbing.Revision(version.Pre[0].VersionStr[1:])
		err := source.cloneAndCheckoutRevision(ctx, source.url, revision, tmpdir)
		if err != nil {
			return nil, -1, err
		}
	} else {
		var ref plumbing.ReferenceName
		if version.Major == 0 && version.Minor == 0 && version.Patch == 0 {
			ref = plumbing.HEAD
		} else {
			ref = plumbing.ReferenceName("refs/tags/v" + version.String())
		}
		err := source.cloneOrPull(ctx, source.url, ref, tmpdir, true /* shallow */)
		if err != nil {

View on GitHub (pinned to 793f7b2e16)

Solutions

  1. Use a pre-release version of the form <semver>-x<commit-sha>, e.g. 1.0.0-xabc1234def
  2. If you meant a stable release, drop the pre-release suffix entirely
  3. Generate the version via the SDK/tooling rather than hand-writing it

Example fix

// before
version := "1.0.0-alpha.beta"
// after
version := "1.0.0-xabc1234" // 'x' + git revision
Defensive patterns

Strategy: validation

Validate before calling

v, err := semver.Parse(versionStr)
if err != nil { return err }
if v.Pre != nil && len(v.Pre) != 1 {
    return fmt.Errorf("pre-release versions must have exactly one segment: <semver>-x<sha>")
}

Type guard

func isHashPrerelease(v semver.Version) bool {
    return len(v.Pre) == 1 && len(v.Pre[0].VersionStr) > 1 && v.Pre[0].VersionStr[0] == 'x'
}

Prevention

When it happens

Trigger: Calling install/download with a version like 1.0.0-alpha.beta (two pre segments) or constructing a pre-release version manually that doesn't follow the x<hash> convention the source expects.

Common situations: Hand-crafting pre-release plugin versions instead of using the SDK's x-prefixed hash scheme; versions copied from other tooling with multi-part pre-release identifiers.

Related errors


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/73f4a7bb3c324db4. Report an issue: GitHub.