From 438d2ad623a9e91df8712cf24c187a380e17fed7 Mon Sep 17 00:00:00 2001 From: Nick Josevski Date: Mon, 31 Aug 2026 12:08:52 +1000 Subject: [PATCH] fix: don't dereference a nil release when the post-create lookup fails ReleaseService.GetByID returns (nil, err) on failure, so the warning branch after a successful create panicked instead of printing the warning: it read Assembled and ReleaseNotes off the nil result. A transient server error immediately after the release was created took the CLI down with a nil pointer dereference. Print the version with empty details instead, and treat a nil release with no error as a lookup failure too. Co-Authored-By: Claude Opus 5 (1M context) --- pkg/cmd/release/create/create.go | 6 ++++- pkg/cmd/release/create/create_test.go | 32 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/release/create/create.go b/pkg/cmd/release/create/create.go index b4b50caa..08cfe0d7 100644 --- a/pkg/cmd/release/create/create.go +++ b/pkg/cmd/release/create/create.go @@ -355,9 +355,13 @@ func createRun(cmd *cobra.Command, f factory.Factory, flags *CreateFlags) error // the API response doesn't tell us what channel it selected, so we need to go look that up to tell the end user newlyCreatedRelease, lookupErr := octopus.Releases.GetByID(options.Response.ReleaseID) + if lookupErr == nil && newlyCreatedRelease == nil { + lookupErr = fmt.Errorf("the server did not return release %s", options.Response.ReleaseID) + } if lookupErr != nil { + // the lookup returns a nil release on failure, so there are no details to print alongside the version cmd.PrintErrf("Warning: cannot fetch release details: %v\n", lookupErr) - printReleaseVersion(options.Response.ReleaseVersion, newlyCreatedRelease.Assembled, newlyCreatedRelease.ReleaseNotes, nil) + printReleaseVersion(options.Response.ReleaseVersion, time.Time{}, "", nil) } else { releaseChan, lookupErr := octopus.Channels.GetByID(newlyCreatedRelease.ChannelID) if lookupErr != nil { diff --git a/pkg/cmd/release/create/create_test.go b/pkg/cmd/release/create/create_test.go index 65ee97c7..64c46819 100644 --- a/pkg/cmd/release/create/create_test.go +++ b/pkg/cmd/release/create/create_test.go @@ -1287,6 +1287,38 @@ func TestReleaseCreate_AutomationMode(t *testing.T) { assert.Equal(t, "", stdErr.String()) }}, + // the post-create lookup returns a nil release on failure, so reading details off it panicked + {"release creation warns, rather than panicking, when the post-create lookup fails", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { + cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { + defer api.Close() + rootCmd.SetArgs([]string{"release", "create", "--project", cacProject.Name}) + return rootCmd.ExecuteC() + }) + + api.ExpectRequest(t, "GET", "/api/").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1").RespondWith(rootResource) + api.ExpectRequest(t, "GET", "/api/Spaces-1/projects/CaC Project").RespondWith(cacProject) + + api.ExpectRequest(t, "POST", "/api/Spaces-1/releases/create/v1").RespondWith(&releases.CreateReleaseResponseV1{ + ReleaseID: "Releases-999", + ReleaseVersion: "1.2.3", + }) + + // the release was created, but the server fails when we go back to ask which channel it picked + api.ExpectRequest(t, "GET", "/api/Spaces-1/releases/Releases-999").RespondWithStatus(500, "Internal Server Error", nil) + + _, err := testutil.ReceivePair(cmdReceiver) + assert.Nil(t, err) + + // the release exists, so we must still report the version we created + assert.Equal(t, heredoc.Doc(` + Successfully created release version 1.2.3 + + View this release on Octopus Deploy: http://server/app#/Spaces-1/releases/Releases-999 + `), stdOut.String()) + assert.Contains(t, stdErr.String(), "Warning: cannot fetch release details:") + }}, + {"release creation specifying custom field", func(t *testing.T, api *testutil.MockHttpServer, rootCmd *cobra.Command, stdOut *bytes.Buffer, stdErr *bytes.Buffer) { cmdReceiver := testutil.GoBegin2(func() (*cobra.Command, error) { defer api.Close()