From 4a3814d497d4e547be0fc34528e2184796066da9 Mon Sep 17 00:00:00 2001 From: Thomas Vilte Date: Thu, 30 Jul 2026 16:58:01 -0300 Subject: [PATCH] feat(release): push tag before publishing --- internal/commands/release/create.go | 13 +++++++++++++ internal/commands/release/create_test.go | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/internal/commands/release/create.go b/internal/commands/release/create.go index c96ec86..e72886b 100644 --- a/internal/commands/release/create.go +++ b/internal/commands/release/create.go @@ -202,6 +202,19 @@ func createReleaseAction(releaseSvc releaseService, trans *i18n.Translations, re } if cmd.Bool("publish") { + // The tag above was only created locally — GitHub can't attach + // a release to a tag it has never seen, and silently falls + // back to an "untagged-..." release instead of erroring. + fmt.Println(trans.GetMessage("release.pushing_tag", 0, struct{ Version string }{release.Version})) + if err := releaseSvc.PushTag(ctx, release.Version); err != nil { + log.Error("failed to push tag before publishing", + "error", err, + "version", release.Version, + "duration_ms", time.Since(start).Milliseconds()) + return fmt.Errorf("%s", trans.GetMessage("release.error_pushing_tag", 0, struct{ Error string }{err.Error()})) + } + fmt.Println(trans.GetMessage("release.push_success", 0, struct{ Version string }{release.Version})) + notes.Changelog = FormatReleaseMarkdown(release, notes, trans) fmt.Println(trans.GetMessage("release.publishing_release", 0, nil)) diff --git a/internal/commands/release/create_test.go b/internal/commands/release/create_test.go index 57c53e6..c97da90 100644 --- a/internal/commands/release/create_test.go +++ b/internal/commands/release/create_test.go @@ -186,6 +186,7 @@ func TestCreateCommand_WithPublish(t *testing.T) { mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil) mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil) mockService.On("TagExists", mock.Anything, mock.Anything).Maybe().Return(false) + mockService.On("PushTag", mock.Anything, "v1.0.0").Return(nil) mockService.On("PublishRelease", mock.Anything, release, notes, false, false, mock.Anything).Return(nil) @@ -205,6 +206,7 @@ func TestCreateCommand_WithPublishAndExistingTag(t *testing.T) { mockService.On("EnrichReleaseContext", mock.Anything, mock.Anything).Return(nil) mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil) mockService.On("TagExists", mock.Anything, "v1.0.0").Return(true) + mockService.On("PushTag", mock.Anything, "v1.0.0").Return(nil) mockService.On("PublishRelease", mock.Anything, release, notes, false, false, mock.Anything).Return(nil) err := runCreateTest(t, "y\n", []string{"--publish"}, mockService) @@ -224,6 +226,7 @@ func TestCreateCommand_WithPublishDraft(t *testing.T) { mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil) mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil) mockService.On("TagExists", mock.Anything, "v1.0.0").Return(false) + mockService.On("PushTag", mock.Anything, "v1.0.0").Return(nil) mockService.On("PublishRelease", mock.Anything, release, notes, true, false, mock.Anything).Return(nil) @@ -244,6 +247,7 @@ func TestCreateCommand_PublishError(t *testing.T) { mockService.On("GenerateReleaseNotes", mock.Anything, release).Return(notes, nil) mockService.On("CreateTag", mock.Anything, "v1.0.0", mock.Anything).Return(nil) mockService.On("TagExists", mock.Anything, "v1.0.0").Return(false) + mockService.On("PushTag", mock.Anything, "v1.0.0").Return(nil) mockService.On("PublishRelease", mock.Anything, release, notes, false, false, mock.Anything).Return(errors.New("publish error"))