Skip to content

Commit 7af1ce4

Browse files
Sayan-cursoragent
andauthored
EOL persistent browsers (#167)
<!-- CURSOR_SUMMARY --> > [!NOTE] > **Medium Risk** > Removes previously supported (and partially backward-compatible) persistent browser session identifiers and project limit flags, which can break existing CLI scripts relying on `--persistent-id` or `--max-persistent-sessions`. Core changes are localized to CLI wiring and output formatting. > > **Overview** > **EOLs persistent browser sessions in the CLI.** The `browsers` commands drop the deprecated persistent-session path: `--persistent-id` is removed, create no longer sends persistence params, list/get output no longer shows “Persistent ID”, and delete now only calls `DeleteByID` (no fallback delete-by-persistent-id). > > **Removes persistent-session project limits.** `projects limits` no longer exposes `--max-persistent-sessions` and stops rendering that limit in table output; tests are updated accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c2440d4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f375eaa commit 7af1ce4

4 files changed

Lines changed: 10 additions & 90 deletions

File tree

cmd/browsers.go

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ type BrowsersService interface {
3535
List(ctx context.Context, query kernel.BrowserListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.BrowserListResponse], err error)
3636
New(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (res *kernel.BrowserNewResponse, err error)
3737
Update(ctx context.Context, id string, body kernel.BrowserUpdateParams, opts ...option.RequestOption) (res *kernel.BrowserUpdateResponse, err error)
38-
Delete(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) (err error)
3938
DeleteByID(ctx context.Context, id string, opts ...option.RequestOption) (err error)
4039
HTTPClient(id string, opts ...option.RequestOption) (*http.Client, error)
4140
LoadExtensions(ctx context.Context, id string, body kernel.BrowserLoadExtensionsParams, opts ...option.RequestOption) (err error)
@@ -175,7 +174,6 @@ func parseViewport(viewport string) (width, height, refreshRate int64, err error
175174

176175
// Inputs for each command
177176
type BrowsersCreateInput struct {
178-
PersistenceID string
179177
TimeoutSeconds int
180178
Stealth BoolFlag
181179
Headless BoolFlag
@@ -291,19 +289,14 @@ func (b BrowsersCmd) List(ctx context.Context, in BrowsersListInput) error {
291289
}
292290

293291
// Prepare table data
294-
headers := []string{"Browser ID", "Created At", "Persistent ID", "Profile", "Pool", "CDP WS URL", "Live View URL"}
292+
headers := []string{"Browser ID", "Created At", "Profile", "Pool", "CDP WS URL", "Live View URL"}
295293
showDeletedAt := in.IncludeDeleted || in.Status == "deleted" || in.Status == "all"
296294
if showDeletedAt {
297295
headers = append(headers, "Deleted At")
298296
}
299297
tableData := pterm.TableData{headers}
300298

301299
for _, browser := range browsers {
302-
persistentID := "-"
303-
if browser.Persistence.ID != "" {
304-
persistentID = browser.Persistence.ID
305-
}
306-
307300
profile := "-"
308301
if browser.Profile.Name != "" {
309302
profile = browser.Profile.Name
@@ -321,7 +314,6 @@ func (b BrowsersCmd) List(ctx context.Context, in BrowsersListInput) error {
321314
row := []string{
322315
browser.SessionID,
323316
util.FormatLocal(browser.CreatedAt),
324-
persistentID,
325317
profile,
326318
pool,
327319
truncateURL(browser.CdpWsURL, 50),
@@ -355,9 +347,6 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
355347
pterm.Info.Println("Creating browser session...")
356348
}
357349
params := kernel.BrowserNewParams{}
358-
if in.PersistenceID != "" {
359-
params.Persistence = kernel.BrowserPersistenceParam{ID: in.PersistenceID}
360-
}
361350
if in.TimeoutSeconds > 0 {
362351
params.TimeoutSeconds = kernel.Opt(int64(in.TimeoutSeconds))
363352
}
@@ -442,17 +431,17 @@ func (b BrowsersCmd) Create(ctx context.Context, in BrowsersCreateInput) error {
442431
return util.PrintPrettyJSON(browser)
443432
}
444433

445-
printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Persistence, browser.Profile, browser.StartURL)
434+
printBrowserSessionResult(browser.SessionID, browser.CdpWsURL, browser.BrowserLiveViewURL, browser.Profile, browser.StartURL)
446435
return nil
447436
}
448437

449-
func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) {
450-
tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, persistence, profile, startURL)
438+
func printBrowserSessionResult(sessionID, cdpURL, liveViewURL string, profile kernel.Profile, startURL string) {
439+
tableData := buildBrowserTableData(sessionID, cdpURL, liveViewURL, profile, startURL)
451440
PrintTableNoPad(tableData, true)
452441
}
453442

454443
// buildBrowserTableData creates a base table with common browser session fields.
455-
func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence kernel.BrowserPersistence, profile kernel.Profile, startURL string) pterm.TableData {
444+
func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, profile kernel.Profile, startURL string) pterm.TableData {
456445
tableData := pterm.TableData{
457446
{"Property", "Value"},
458447
{"Session ID", sessionID},
@@ -461,9 +450,6 @@ func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence ke
461450
if liveViewURL != "" {
462451
tableData = append(tableData, []string{"Live View URL", liveViewURL})
463452
}
464-
if persistence.ID != "" {
465-
tableData = append(tableData, []string{"Persistent ID", persistence.ID})
466-
}
467453
if profile.ID != "" || profile.Name != "" {
468454
profVal := profile.Name
469455
if profVal == "" {
@@ -478,29 +464,10 @@ func buildBrowserTableData(sessionID, cdpURL, liveViewURL string, persistence ke
478464
}
479465

480466
func (b BrowsersCmd) Delete(ctx context.Context, in BrowsersDeleteInput) error {
481-
// Try both deletion modes without confirmation
482467
// Treat not found as a success (idempotent delete)
483-
var nonNotFoundErrors []error
484-
485-
// Attempt by session ID
486-
if err := b.browsers.DeleteByID(ctx, in.Identifier); err != nil {
487-
if !util.IsNotFound(err) {
488-
nonNotFoundErrors = append(nonNotFoundErrors, err)
489-
}
490-
}
491-
492-
// Attempt by persistent ID (backward compatibility)
493-
if err := b.browsers.Delete(ctx, kernel.BrowserDeleteParams{PersistentID: in.Identifier}); err != nil {
494-
if !util.IsNotFound(err) {
495-
nonNotFoundErrors = append(nonNotFoundErrors, err)
496-
}
497-
}
498-
499-
if len(nonNotFoundErrors) >= 2 {
500-
// Both failed with meaningful errors; report one
501-
return util.CleanedUpSdkError{Err: nonNotFoundErrors[0]}
468+
if err := b.browsers.DeleteByID(ctx, in.Identifier); err != nil && !util.IsNotFound(err) {
469+
return util.CleanedUpSdkError{Err: err}
502470
}
503-
504471
pterm.Success.Printf("Successfully deleted (or already absent) browser: %s\n", in.Identifier)
505472
return nil
506473
}
@@ -562,7 +529,6 @@ func (b BrowsersCmd) Get(ctx context.Context, in BrowsersGetInput) error {
562529
browser.SessionID,
563530
browser.CdpWsURL,
564531
browser.BrowserLiveViewURL,
565-
browser.Persistence,
566532
browser.Profile,
567533
browser.StartURL,
568534
)
@@ -2524,8 +2490,6 @@ func init() {
25242490

25252491
// Add flags for create command
25262492
browsersCreateCmd.Flags().StringP("output", "o", "", "Output format: json for raw API response")
2527-
browsersCreateCmd.Flags().StringP("persistent-id", "p", "", "[DEPRECATED] Use --timeout and profiles instead. Unique identifier for browser session persistence")
2528-
_ = browsersCreateCmd.Flags().MarkDeprecated("persistent-id", "use --timeout (up to 72 hours) and profiles instead")
25292493
browsersCreateCmd.Flags().BoolP("stealth", "s", false, "Launch browser in stealth mode to avoid detection")
25302494
browsersCreateCmd.Flags().BoolP("headless", "H", false, "Launch browser without GUI access")
25312495
browsersCreateCmd.Flags().Bool("gpu", false, "Launch browser with hardware-accelerated GPU rendering")
@@ -2595,10 +2559,6 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
25952559
client := getKernelClient(cmd)
25962560

25972561
// Get flag values
2598-
persistenceID, _ := cmd.Flags().GetString("persistent-id")
2599-
if persistenceID != "" {
2600-
pterm.Warning.Println("--persistent-id is deprecated. Use --timeout (up to 72 hours) and profiles instead.")
2601-
}
26022562
stealthVal, _ := cmd.Flags().GetBool("stealth")
26032563
headlessVal, _ := cmd.Flags().GetBool("headless")
26042564
gpuVal, _ := cmd.Flags().GetBool("gpu")
@@ -2689,7 +2649,7 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
26892649
if output == "json" {
26902650
return util.PrintPrettyJSON(resp)
26912651
}
2692-
printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Persistence, resp.Profile, resp.StartURL)
2652+
printBrowserSessionResult(resp.SessionID, resp.CdpWsURL, resp.BrowserLiveViewURL, resp.Profile, resp.StartURL)
26932653
return nil
26942654
}
26952655

@@ -2711,7 +2671,6 @@ func runBrowsersCreate(cmd *cobra.Command, args []string) error {
27112671
}
27122672

27132673
in := BrowsersCreateInput{
2714-
PersistenceID: persistenceID,
27152674
TimeoutSeconds: timeout,
27162675
Stealth: BoolFlag{Set: cmd.Flags().Changed("stealth"), Value: stealthVal},
27172676
Headless: BoolFlag{Set: cmd.Flags().Changed("headless"), Value: headlessVal},

cmd/browsers_test.go

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ type FakeBrowsersService struct {
6060
ListFunc func(ctx context.Context, query kernel.BrowserListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.BrowserListResponse], error)
6161
NewFunc func(ctx context.Context, body kernel.BrowserNewParams, opts ...option.RequestOption) (*kernel.BrowserNewResponse, error)
6262
UpdateFunc func(ctx context.Context, id string, body kernel.BrowserUpdateParams, opts ...option.RequestOption) (*kernel.BrowserUpdateResponse, error)
63-
DeleteFunc func(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) error
6463
DeleteByIDFunc func(ctx context.Context, id string, opts ...option.RequestOption) error
6564
HTTPClientFunc func(id string, opts ...option.RequestOption) (*http.Client, error)
6665
LoadExtensionsFunc func(ctx context.Context, id string, body kernel.BrowserLoadExtensionsParams, opts ...option.RequestOption) error
@@ -94,13 +93,6 @@ func (f *FakeBrowsersService) Update(ctx context.Context, id string, body kernel
9493
return &kernel.BrowserUpdateResponse{}, nil
9594
}
9695

97-
func (f *FakeBrowsersService) Delete(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) error {
98-
if f.DeleteFunc != nil {
99-
return f.DeleteFunc(ctx, body, opts...)
100-
}
101-
return nil
102-
}
103-
10496
func (f *FakeBrowsersService) DeleteByID(ctx context.Context, id string, opts ...option.RequestOption) error {
10597
if f.DeleteByIDFunc != nil {
10698
return f.DeleteByIDFunc(ctx, id, opts...)
@@ -400,14 +392,12 @@ func TestBrowsersList_PrintsTableWithRows(t *testing.T) {
400392
CdpWsURL: "ws://cdp-1",
401393
BrowserLiveViewURL: "http://view-1",
402394
CreatedAt: created,
403-
Persistence: kernel.BrowserPersistence{ID: "pid-1"},
404395
},
405396
{
406397
SessionID: "sess-2",
407398
CdpWsURL: "ws://cdp-2",
408399
BrowserLiveViewURL: "",
409400
CreatedAt: created,
410-
Persistence: kernel.BrowserPersistence{ID: ""},
411401
},
412402
}
413403

@@ -422,7 +412,6 @@ func TestBrowsersList_PrintsTableWithRows(t *testing.T) {
422412
out := outBuf.String()
423413
assert.Contains(t, out, "sess-1")
424414
assert.Contains(t, out, "sess-2")
425-
assert.Contains(t, out, "pid-1")
426415
}
427416

428417
func TestBrowsersList_PrintsErrorOnFailure(t *testing.T) {
@@ -469,15 +458,13 @@ func TestBrowsersCreate_PrintsResponse(t *testing.T) {
469458
SessionID: "sess-new",
470459
CdpWsURL: "ws://cdp-new",
471460
BrowserLiveViewURL: "http://view-new",
472-
Persistence: kernel.BrowserPersistence{ID: "pid-new"},
473461
}
474462
return resp, nil
475463
},
476464
}
477465

478466
b := BrowsersCmd{browsers: fake}
479467
in := BrowsersCreateInput{
480-
PersistenceID: "pid-new",
481468
TimeoutSeconds: 120,
482469
Stealth: BoolFlag{Set: true, Value: true},
483470
Headless: BoolFlag{Set: true, Value: false},
@@ -491,8 +478,6 @@ func TestBrowsersCreate_PrintsResponse(t *testing.T) {
491478
assert.Contains(t, out, "ws://cdp-new")
492479
assert.Contains(t, out, "Live View URL")
493480
assert.Contains(t, out, "http://view-new")
494-
assert.Contains(t, out, "Persistent ID")
495-
assert.Contains(t, out, "pid-new")
496481
}
497482

498483
func TestBrowsersCreate_WithInvocationID(t *testing.T) {
@@ -533,9 +518,6 @@ func TestBrowsersDelete_Success(t *testing.T) {
533518
setupStdoutCapture(t)
534519

535520
fake := &FakeBrowsersService{
536-
DeleteFunc: func(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) error {
537-
return nil
538-
},
539521
DeleteByIDFunc: func(ctx context.Context, id string, opts ...option.RequestOption) error {
540522
return nil
541523
},
@@ -551,19 +533,15 @@ func TestBrowsersDelete_Failure(t *testing.T) {
551533
setupStdoutCapture(t)
552534

553535
fake := &FakeBrowsersService{
554-
DeleteFunc: func(ctx context.Context, body kernel.BrowserDeleteParams, opts ...option.RequestOption) error {
555-
return errors.New("left failed")
556-
},
557536
DeleteByIDFunc: func(ctx context.Context, id string, opts ...option.RequestOption) error {
558-
return errors.New("right failed")
537+
return errors.New("delete failed")
559538
},
560539
}
561540
b := BrowsersCmd{browsers: fake}
562541
err := b.Delete(context.Background(), BrowsersDeleteInput{Identifier: "any"})
563542

564543
assert.Error(t, err)
565-
errMsg := err.Error()
566-
assert.True(t, strings.Contains(errMsg, "right failed") || strings.Contains(errMsg, "left failed"), "expected error message to contain either 'right failed' or 'left failed', got: %s", errMsg)
544+
assert.Contains(t, err.Error(), "delete failed")
567545
}
568546

569547
func TestBrowsersView_ByID_PrintsURL(t *testing.T) {
@@ -656,7 +634,6 @@ func TestBrowsersGet_PrintsDetails(t *testing.T) {
656634
Stealth: true,
657635
KioskMode: false,
658636
Viewport: shared.BrowserViewport{Width: 1920, Height: 1080, RefreshRate: 25},
659-
Persistence: kernel.BrowserPersistence{ID: "persist-id"},
660637
Profile: kernel.Profile{ID: "prof-id", Name: "my-profile"},
661638
ProxyID: "proxy-123",
662639
}, nil
@@ -673,7 +650,6 @@ func TestBrowsersGet_PrintsDetails(t *testing.T) {
673650
assert.Contains(t, out, "false") // Headless
674651
assert.Contains(t, out, "true") // Stealth
675652
assert.Contains(t, out, "1920x1080@25")
676-
assert.Contains(t, out, "persist-id")
677653
assert.Contains(t, out, "my-profile")
678654
assert.Contains(t, out, "proxy-123")
679655
}

cmd/projects.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ type ProjectsLimitsGetInput struct {
5757
type ProjectsLimitsSetInput struct {
5858
Identifier string
5959
MaxConcurrentSessions Int64Flag
60-
MaxPersistentSessions Int64Flag
6160
MaxConcurrentInvocations Int64Flag
6261
MaxPooledSessions Int64Flag
6362
Output string
@@ -193,12 +192,6 @@ func (c ProjectsCmd) LimitsSet(ctx context.Context, in ProjectsLimitsSetInput) e
193192
}
194193
inner.MaxConcurrentSessions = param.NewOpt(in.MaxConcurrentSessions.Value)
195194
}
196-
if in.MaxPersistentSessions.Set {
197-
if in.MaxPersistentSessions.Value < 0 {
198-
return fmt.Errorf("--max-persistent-sessions must be non-negative (got %d); use 0 to remove the cap", in.MaxPersistentSessions.Value)
199-
}
200-
inner.MaxPersistentSessions = param.NewOpt(in.MaxPersistentSessions.Value)
201-
}
202195
if in.MaxConcurrentInvocations.Set {
203196
if in.MaxConcurrentInvocations.Value < 0 {
204197
return fmt.Errorf("--max-concurrent-invocations must be non-negative (got %d); use 0 to remove the cap", in.MaxConcurrentInvocations.Value)
@@ -243,7 +236,6 @@ func renderProjectLimits(limits *kernel.ProjectLimits) {
243236
rows := pterm.TableData{
244237
{"Limit", "Value"},
245238
{"Max Concurrent Sessions", formatProjectLimitValue(limits.MaxConcurrentSessions, limits.JSON.MaxConcurrentSessions)},
246-
{"Max Persistent Sessions", formatProjectLimitValue(limits.MaxPersistentSessions, limits.JSON.MaxPersistentSessions)},
247239
{"Max Concurrent Invocations", formatProjectLimitValue(limits.MaxConcurrentInvocations, limits.JSON.MaxConcurrentInvocations)},
248240
{"Max Pooled Sessions", formatProjectLimitValue(limits.MaxPooledSessions, limits.JSON.MaxPooledSessions)},
249241
}
@@ -297,7 +289,6 @@ func runProjectsLimitsGet(cmd *cobra.Command, args []string) error {
297289
func runProjectsLimitsSet(cmd *cobra.Command, args []string) error {
298290
c := getProjectsHandler(cmd)
299291
maxConcurrentSessions, _ := cmd.Flags().GetInt64("max-concurrent-sessions")
300-
maxPersistentSessions, _ := cmd.Flags().GetInt64("max-persistent-sessions")
301292
maxConcurrentInvocations, _ := cmd.Flags().GetInt64("max-concurrent-invocations")
302293
maxPooledSessions, _ := cmd.Flags().GetInt64("max-pooled-sessions")
303294
output, _ := cmd.Flags().GetString("output")
@@ -308,10 +299,6 @@ func runProjectsLimitsSet(cmd *cobra.Command, args []string) error {
308299
Set: cmd.Flags().Changed("max-concurrent-sessions"),
309300
Value: maxConcurrentSessions,
310301
},
311-
MaxPersistentSessions: Int64Flag{
312-
Set: cmd.Flags().Changed("max-persistent-sessions"),
313-
Value: maxPersistentSessions,
314-
},
315302
MaxConcurrentInvocations: Int64Flag{
316303
Set: cmd.Flags().Changed("max-concurrent-invocations"),
317304
Value: maxConcurrentInvocations,
@@ -330,7 +317,6 @@ func addProjectsLimitsOutputFlag(cmd *cobra.Command) {
330317

331318
func addProjectsLimitsSetFlags(cmd *cobra.Command) {
332319
cmd.Flags().Int64("max-concurrent-sessions", 0, "Maximum concurrent browser sessions (0 to remove cap)")
333-
cmd.Flags().Int64("max-persistent-sessions", 0, "Maximum persistent browser sessions (0 to remove cap)")
334320
cmd.Flags().Int64("max-concurrent-invocations", 0, "Maximum concurrent app invocations (0 to remove cap)")
335321
cmd.Flags().Int64("max-pooled-sessions", 0, "Maximum pooled sessions capacity (0 to remove cap)")
336322
addProjectsLimitsOutputFlag(cmd)

cmd/projects_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ func TestProjectsLimitsGet_DefaultOutput(t *testing.T) {
100100
}
101101
limits.JSON.MaxConcurrentSessions = respjson.NewField("10")
102102
limits.JSON.MaxConcurrentInvocations = respjson.NewField("5")
103-
limits.JSON.MaxPersistentSessions = respjson.NewField(respjson.Null)
104103

105104
fakeProjects := &FakeProjectsService{}
106105
fakeLimits := &FakeProjectLimitsService{

0 commit comments

Comments
 (0)