Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cmd/ls/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ func displayLsResetBreadCrumb(t *terminal.Terminal, workspaces []entity.Workspac
}
}
if foundAResettableWorkspace {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reset breadcrumb now prints only a blank line when a resettable instance is found. Since --hard was removed, this would be a good place to replace the old guidance with an actionable next step (e.g., suggest brev recreate <name> if brev reset doesn’t resolve the issue) rather than emitting an empty line.

Suggested change
if foundAResettableWorkspace {
if foundAResettableWorkspace {
t.Vprintf("%s", t.Green("If reset doesn't resolve the issue, recreate the instance:\n"))
for _, w := range workspaces {
if w.Status == entity.Failure || getWorkspaceDisplayStatus(w) == entity.Unhealthy {
t.Vprintf("%s", t.Yellow(fmt.Sprintf("\tbrev recreate %s\n", w.Name)))
}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally left blank. The prior --hard breadcrumb caused a production incident (nemoclaw-f4a386 / BREV-9066 / the thread in #ep-brev-support) because an AI coding agent auto-executed the destructive suggestion. brev recreate is equally destructive (delete + create), so suggesting it from ls output re-introduces the same vector. Users who need a recreate can discover it via brev --help or docs, which requires a deliberate action rather than reading ls output.

t.Vprintf("%s", t.Yellow("If this problem persists, run the command again with the --hard flag (warning: the --hard flag will not preserve uncommitted files!) \n\n"))
t.Vprintf("\n")
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change alters the user-facing help text emitted by displayLsResetBreadCrumb, but there doesn’t appear to be any test asserting the breadcrumb output for unhealthy/failed instances in ls_test.go. Adding a focused test that captures the printed guidance would help prevent future regressions in CLI output.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as #1 — the line was kept empty on purpose to avoid any CLI output that could confuse the user or be auto-executed by an agent.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we just remove the print statement altogether? what's the value in printing a new line?

Copy link
Copy Markdown
Contributor

@arush070 arush070 Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, bare newline removed. Can we get a review and approval after the new push?
@patelspratik @stephahart

}
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/cmd/recreate/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ recreate a workspace with the name `naive-pubsub`

```
$ brev recreate payments-frontend
Starting hard reset 🤙 This can take a couple of minutes.
recreating 🤙 This can take a couple of minutes.

Deleting workspace - naive-pubsub.
Workspace is starting. This can take up to 2 minutes the first time.
Deleting instance - naive-pubsub.
Instance is starting. This can take up to 2 minutes the first time.
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recreate example is inconsistent: it runs brev recreate payments-frontend but the output references naive-pubsub. Update the command and output to use the same workspace/instance name throughout the example to avoid confusing users.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a new commit to address this.

name naive-pubsub
template v7nd45zsc Admin
resource class 4x16
Expand Down
18 changes: 9 additions & 9 deletions pkg/cmd/recreate/recreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ func NewCmdRecreate(t *terminal.Terminal, store recreateStore) *cobra.Command {

func RunRecreate(t *terminal.Terminal, args []string, recreateStore recreateStore) error {
for _, arg := range args {
err := hardResetProcess(arg, t, recreateStore)
err := recreateProcess(arg, t, recreateStore)
if err != nil {
return breverrors.WrapAndTrace(err)
}
}
return nil
}

// hardResetProcess deletes an existing workspace and creates a new one
func hardResetProcess(workspaceName string, t *terminal.Terminal, recreateStore recreateStore) error {
// recreateProcess deletes an existing workspace and creates a new one
func recreateProcess(workspaceName string, t *terminal.Terminal, recreateStore recreateStore) error {
t.Vprint(t.Green("recreating 🤙 " + t.Yellow("This can take a couple of minutes.\n")))
workspace, err := util.GetUserWorkspaceByNameOrIDErr(recreateStore, workspaceName)
if err != nil {
Expand All @@ -78,21 +78,21 @@ func hardResetProcess(workspaceName string, t *terminal.Terminal, recreateStore
time.Sleep(10 * time.Second)

if len(deletedWorkspace.GitRepo) != 0 {
err := hardResetCreateWorkspaceFromRepo(t, recreateStore, deletedWorkspace)
err := createWorkspaceFromRepo(t, recreateStore, deletedWorkspace)
if err != nil {
return breverrors.WrapAndTrace(err)
}
} else {
err := hardResetCreateEmptyWorkspace(t, recreateStore, deletedWorkspace)
err := createEmptyWorkspace(t, recreateStore, deletedWorkspace)
if err != nil {
return breverrors.WrapAndTrace(err)
}
}
return nil
}

// hardResetCreateWorkspaceFromRepo clone a GIT repository, triggeres from the --hardreset flag
func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error {
// createWorkspaceFromRepo recreates a workspace from its git repo source.
func createWorkspaceFromRepo(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error {
t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time."))
var orgID string
activeorg, err := recreateStore.GetActiveOrganizationOrDefault()
Expand Down Expand Up @@ -133,8 +133,8 @@ func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, recreateStore recrea
return nil
}

// hardResetCreateEmptyWorkspace creates a new empty worksapce, triggered from the --hardreset flag
func hardResetCreateEmptyWorkspace(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error {
// createEmptyWorkspace recreates an empty workspace (no git repo).
func createEmptyWorkspace(t *terminal.Terminal, recreateStore recreateStore, workspace *entity.Workspace) error {
t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.\n"))

// ensure name
Expand Down
193 changes: 3 additions & 190 deletions pkg/cmd/reset/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,10 @@ package reset

import (
_ "embed"
"strings"
"time"

"github.com/brevdev/brev-cli/pkg/cmd/completions"
"github.com/brevdev/brev-cli/pkg/cmd/util"
"github.com/brevdev/brev-cli/pkg/config"
"github.com/brevdev/brev-cli/pkg/entity"
"github.com/brevdev/brev-cli/pkg/featureflag"
"github.com/brevdev/brev-cli/pkg/store"
"github.com/brevdev/brev-cli/pkg/terminal"

breverrors "github.com/brevdev/brev-cli/pkg/errors"
Expand All @@ -28,16 +23,9 @@ type ResetStore interface {
completions.CompletionStore
util.GetWorkspaceByNameOrIDErrStore
ResetWorkspace(workspaceID string) (*entity.Workspace, error)
GetActiveOrganizationOrDefault() (*entity.Organization, error)
GetCurrentUser() (*entity.User, error)
CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error)
GetWorkspace(id string) (*entity.Workspace, error)
DeleteWorkspace(workspaceID string) (*entity.Workspace, error)
}

func NewCmdReset(t *terminal.Terminal, loginResetStore ResetStore, noLoginResetStore ResetStore) *cobra.Command {
var hardreset bool

cmd := &cobra.Command{
Annotations: map[string]string{"provider-dependent": ""},
Use: "reset",
Expand All @@ -48,192 +36,17 @@ func NewCmdReset(t *terminal.Terminal, loginResetStore ResetStore, noLoginResetS
ValidArgsFunction: completions.GetAllWorkspaceNameCompletionHandler(noLoginResetStore, t),
RunE: func(cmd *cobra.Command, args []string) error {
for _, arg := range args {
if hardreset {
err := hardResetProcess(arg, t, loginResetStore)
if err != nil {
return breverrors.WrapAndTrace(err)
}
} else {
err := resetWorkspace(arg, t, loginResetStore)
if err != nil {
return breverrors.WrapAndTrace(err)
}
err := resetWorkspace(arg, t, loginResetStore)
if err != nil {
return breverrors.WrapAndTrace(err)
}
}
return nil
},
}

cmd.Flags().BoolVarP(&hardreset, "hard", "", false, "DEPRECATED: use brev recreate")
return cmd
}

// hardResetProcess deletes an existing workspace and creates a new one
func hardResetProcess(workspaceName string, t *terminal.Terminal, resetStore ResetStore) error {
t.Vprint(t.Green("Starting hard reset 🤙 " + t.Yellow("This can take a couple of minutes.\n")))
workspace, err := util.GetUserWorkspaceByNameOrIDErr(resetStore, workspaceName)
if err != nil {
return breverrors.WrapAndTrace(err)
}

deletedWorkspace, err := resetStore.DeleteWorkspace(workspace.ID)
if err != nil {
return breverrors.WrapAndTrace(err)
}

t.Vprint(t.Yellow("Deleting instance - %s.", deletedWorkspace.Name))
time.Sleep(10 * time.Second)

if len(deletedWorkspace.GitRepo) != 0 {
err := hardResetCreateWorkspaceFromRepo(t, resetStore, deletedWorkspace)
if err != nil {
return breverrors.WrapAndTrace(err)
}
} else {
err := hardResetCreateEmptyWorkspace(t, resetStore, deletedWorkspace)
if err != nil {
return breverrors.WrapAndTrace(err)
}
}
t.Vprint(t.Red("NOTE: THIS COMMAND IS DEPRECATED"))
t.Vprint(t.Red("It still worked, but use brev recreate " + workspaceName + " next time"))
return nil
}

// hardResetCreateWorkspaceFromRepo clone a GIT repository, triggeres from the --hardreset flag
func hardResetCreateWorkspaceFromRepo(t *terminal.Terminal, resetStore ResetStore, workspace *entity.Workspace) error {
t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time."))
var orgID string
activeorg, err := resetStore.GetActiveOrganizationOrDefault()
if err != nil {
return breverrors.WrapAndTrace(err)
}
if activeorg == nil {
return breverrors.NewValidationError("no org exist")
}
orgID = activeorg.ID
clusterID := config.GlobalConfig.GetDefaultClusterID()
options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name).WithGitRepo(workspace.GitRepo)

user, err := resetStore.GetCurrentUser()
if err != nil {
return breverrors.WrapAndTrace(err)
}

options = resolveWorkspaceUserOptions(options, user)

options.StartupScriptPath = workspace.StartupScriptPath
options.Execs = workspace.ExecsV0
options.Repos = workspace.ReposV0
options.IDEConfig = &workspace.IDEConfig

w, err := resetStore.CreateWorkspace(orgID, options)
if err != nil {
return breverrors.WrapAndTrace(err)
}

err = pollUntil(t, w.ID, entity.Running, resetStore, true)
if err != nil {
return breverrors.WrapAndTrace(err)
}

t.Vprint(t.Green("\nYour instance is ready!"))
t.Vprintf("%s", t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier()))
return nil
}

// hardResetCreateEmptyWorkspace creates a new empty worksapce, triggered from the --hardreset flag
func hardResetCreateEmptyWorkspace(t *terminal.Terminal, resetStore ResetStore, workspace *entity.Workspace) error {
t.Vprint(t.Green("Instance is starting. ") + t.Yellow("This can take up to 2 minutes the first time.\n"))

// ensure name
if len(workspace.Name) == 0 {
return breverrors.NewValidationError("name field is required for empty instances")
}

// ensure org
var orgID string
activeorg, err := resetStore.GetActiveOrganizationOrDefault()
if err != nil {
return breverrors.WrapAndTrace(err)
}
if activeorg == nil {
return breverrors.NewValidationError("no org exist")
}
orgID = activeorg.ID
clusterID := config.GlobalConfig.GetDefaultClusterID()
options := store.NewCreateWorkspacesOptions(clusterID, workspace.Name)

user, err := resetStore.GetCurrentUser()
if err != nil {
return breverrors.WrapAndTrace(err)
}

options = resolveWorkspaceUserOptions(options, user)

options.StartupScriptPath = workspace.StartupScriptPath
options.Execs = workspace.ExecsV0
options.Repos = workspace.ReposV0
options.IDEConfig = &workspace.IDEConfig

w, err := resetStore.CreateWorkspace(orgID, options)
if err != nil {
return breverrors.WrapAndTrace(err)
}

err = pollUntil(t, w.ID, entity.Running, resetStore, true)
if err != nil {
return breverrors.WrapAndTrace(err)
}

t.Vprint(t.Green("\nYour instance is ready!"))
t.Vprintf("%s", t.Green("\nSSH into your machine:\n\tssh %s\n", w.GetLocalIdentifier()))

return nil
}

func pollUntil(t *terminal.Terminal, wsid string, state string, resetStore ResetStore, canSafelyExit bool) error {
s := t.NewSpinner()
isReady := false
if canSafelyExit {
t.Vprintf("You can safely ctrl+c to exit\n")
}
s.Suffix = " hang tight 🤙"
s.Start()
for !isReady {
time.Sleep(5 * time.Second)
ws, err := resetStore.GetWorkspace(wsid)
if err != nil {
return breverrors.WrapAndTrace(err)
}
s.Suffix = " instance is " + strings.ToLower(ws.Status)
if ws.Status == state {
s.Suffix = "Instance is ready!"
s.Stop()
isReady = true
}
}
return nil
}

func resolveWorkspaceUserOptions(options *store.CreateWorkspacesOptions, user *entity.User) *store.CreateWorkspacesOptions {
if options.WorkspaceTemplateID == "" {
if featureflag.IsAdmin(user.GlobalUserType) {
options.WorkspaceTemplateID = store.DevWorkspaceTemplateID
} else {
options.WorkspaceTemplateID = store.UserWorkspaceTemplateID
}
}
if options.WorkspaceClassID == "" {
if featureflag.IsAdmin(user.GlobalUserType) {
options.WorkspaceClassID = store.DevWorkspaceClassID
} else {
options.WorkspaceClassID = store.UserWorkspaceClassID
}
}
return options
}

func resetWorkspace(workspaceName string, t *terminal.Terminal, resetStore ResetStore) error {
workspace, err := util.GetUserWorkspaceByNameOrIDErr(resetStore, workspaceName)
if err != nil {
Expand Down
Loading