From d7a360c2e38db448d4a2c4e1746c1dda5941dc01 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:56:03 +0000 Subject: [PATCH 1/2] Strip profile session restore when configure batch carries a start_url --- server/cmd/api/api/chromium_configure.go | 20 +++++++++++++++++++ server/cmd/api/api/chromium_configure_test.go | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/server/cmd/api/api/chromium_configure.go b/server/cmd/api/api/chromium_configure.go index 7d995314..630a374e 100644 --- a/server/cmd/api/api/chromium_configure.go +++ b/server/cmd/api/api/chromium_configure.go @@ -158,6 +158,11 @@ func (s *ApiService) ChromiumConfigure(ctx context.Context, request oapi.Chromiu if err != nil { return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()), nil } + if spec.needsNav { + if err := stripProfileSessionRestore(preparedProfile); err != nil { + return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()), nil + } + } if err := chromiumInstallPreparedProfile(preparedProfile); err != nil { return cfg500ConfigureStep(chromiumConfigureStepProfile, err.Error()), nil } @@ -564,6 +569,21 @@ func chromiumPrepareProfileArchive(profilePath string, strip int) (preparedDir s return preparedDir, cleanup, nil } +// stripProfileSessionRestore deletes the prepared profile's Default/Sessions +// directory so Chrome does not restore the profile's saved tabs on the restart +// that follows. Only called when the same configure batch carries a start_url: +// Chrome restores tabs asynchronously after DevTools comes up, so a restored +// tab can appear after the start_url dispatch has enumerated (and closed) page +// targets, leaving the browser on a profile tab instead of the requested page. +// Without a start_url the directory is kept and tabs restore as usual. Only +// the live copy is touched; the stored profile archive is unchanged. +func stripProfileSessionRestore(preparedDir string) error { + if err := os.RemoveAll(filepath.Join(preparedDir, "Default", "Sessions")); err != nil { + return fmt.Errorf("strip profile session restore: %w", err) + } + return nil +} + func chromiumInstallPreparedProfile(preparedDir string) error { if preparedDir == "" { return nil diff --git a/server/cmd/api/api/chromium_configure_test.go b/server/cmd/api/api/chromium_configure_test.go index e4f44f04..eac0e5f1 100644 --- a/server/cmd/api/api/chromium_configure_test.go +++ b/server/cmd/api/api/chromium_configure_test.go @@ -5,6 +5,8 @@ import ( "errors" "io" "mime/multipart" + "os" + "path/filepath" "strings" "testing" @@ -76,6 +78,24 @@ func TestChromiumStartURLSpec(t *testing.T) { require.NotEmpty(t, errs) } +func TestStripProfileSessionRestore(t *testing.T) { + prepared := t.TempDir() + sessions := filepath.Join(prepared, "Default", "Sessions") + require.NoError(t, os.MkdirAll(sessions, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(sessions, "Session_123"), []byte("tabs"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(prepared, "Default", "Preferences"), []byte("{}"), 0o644)) + + require.NoError(t, stripProfileSessionRestore(prepared)) + + _, err := os.Stat(sessions) + require.True(t, os.IsNotExist(err)) + _, err = os.Stat(filepath.Join(prepared, "Default", "Preferences")) + require.NoError(t, err) + + // Absent Sessions directory is a no-op, not an error. + require.NoError(t, stripProfileSessionRestore(prepared)) +} + func TestChromiumValidateFlags(t *testing.T) { valid := `{"flags":["--kiosk"]}` plan, err := chromiumValidateFlags(&valid) From aa17d7b858f62a101901bf64c475ba019807c065 Mon Sep 17 00:00:00 2001 From: yummybomb <19238148+yummybomb@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:41:51 +0000 Subject: [PATCH 2/2] Trim stripProfileSessionRestore doc comment --- server/cmd/api/api/chromium_configure.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/server/cmd/api/api/chromium_configure.go b/server/cmd/api/api/chromium_configure.go index 630a374e..928d3466 100644 --- a/server/cmd/api/api/chromium_configure.go +++ b/server/cmd/api/api/chromium_configure.go @@ -569,14 +569,9 @@ func chromiumPrepareProfileArchive(profilePath string, strip int) (preparedDir s return preparedDir, cleanup, nil } -// stripProfileSessionRestore deletes the prepared profile's Default/Sessions -// directory so Chrome does not restore the profile's saved tabs on the restart -// that follows. Only called when the same configure batch carries a start_url: -// Chrome restores tabs asynchronously after DevTools comes up, so a restored -// tab can appear after the start_url dispatch has enumerated (and closed) page -// targets, leaving the browser on a profile tab instead of the requested page. -// Without a start_url the directory is kept and tabs restore as usual. Only -// the live copy is touched; the stored profile archive is unchanged. +// stripProfileSessionRestore deletes the prepared profile's Default/Sessions so +// Chrome cannot restore its saved tabs after the restart and race the start_url +// navigation. Only the live copy is touched; the stored archive is unchanged. func stripProfileSessionRestore(preparedDir string) error { if err := os.RemoveAll(filepath.Join(preparedDir, "Default", "Sessions")); err != nil { return fmt.Errorf("strip profile session restore: %w", err)