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
- Use a pre-release version of the form <semver>-x<commit-sha>, e.g. 1.0.0-xabc1234def
- If you meant a stable release, drop the pre-release suffix entirely
- 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
- Always build pre-release plugin versions as <semver>-x<commit-sha>
- Never hand-edit pre-release version strings; use SDK tooling
- Prefer stable releases unless testing a specific commit build
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
- illegal semver returned by language host: %s@%s: %w
- GetLatestVersion is not supported for plugins from http sour
- invalid plugin version %s: %w
- module does not have semver compatible version
- loading package '%v': %w
AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31).
Data as JSON: /api/errors/73f4a7bb3c324db4.
Report an issue: GitHub.