From de66ba9457833e154ed72c458149036cb9ada38d Mon Sep 17 00:00:00 2001 From: "Theodor S. Midtlien" Date: Tue, 15 Sep 2026 16:10:08 +0200 Subject: [PATCH] Fix empty active state read --- client/internal/debug/debug.go | 7 +++++-- client/internal/debug/debug_test.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client/internal/debug/debug.go b/client/internal/debug/debug.go index 20dc87b7e..d5fcca0cd 100644 --- a/client/internal/debug/debug.go +++ b/client/internal/debug/debug.go @@ -746,7 +746,9 @@ func (g *BundleGenerator) addProfiles() error { return fmt.Errorf("add profiles file to zip: %w", err) } - if len(activeRaw) == 0 { + // A file that is present but empty still says something: the state was + // truncated rather than never written. + if activeRaw == nil { return nil } if err := g.addFileToZip(bytes.NewReader(activeRaw), activeProfileBundleFile); err != nil { @@ -759,7 +761,8 @@ func (g *BundleGenerator) addProfiles() error { // readActiveProfileState reads the state file directly rather than through // ServiceManager, whose getters seed a default one when it is missing. Bundle // collection must not write the state it reports on. The raw bytes come back -// even when parsing fails, so a corrupted file still reaches the bundle. +// even when parsing fails, so a corrupted file still reaches the bundle, and +// they are nil only when nothing was read at all. func readActiveProfileState() (*profilemanager.ActiveProfileState, []byte, error) { data, err := os.ReadFile(profilemanager.ActiveProfileStatePath) if err != nil { diff --git a/client/internal/debug/debug_test.go b/client/internal/debug/debug_test.go index d9ca34bb1..c4510269e 100644 --- a/client/internal/debug/debug_test.go +++ b/client/internal/debug/debug_test.go @@ -1105,6 +1105,21 @@ func TestAddProfiles(t *testing.T) { assert.NotContains(t, files, activeProfileBundleFile, "no state file means nothing to dump") }) + t.Run("keeps an empty active profile state file", func(t *testing.T) { + dir := setupProfilesDir(t) + writeProfileJSON(t, filepath.Join(dir, "alice", "aaaa1111.json"), "work", []string{"uid:1000"}) + // A truncated write leaves the file in place with no content. The bundle + // has to show that, not look like the file was never written. + require.NoError(t, os.WriteFile(profilemanager.ActiveProfileStatePath, nil, 0o600)) + + files := bundleFiles(t, (*BundleGenerator).addProfiles) + + raw, ok := files[activeProfileBundleFile] + assert.True(t, ok, "an empty state file should still be dumped") + assert.Empty(t, raw) + assert.Contains(t, files[profilesBundleFile], "Active profile: unknown") + }) + t.Run("records a parse error and keeps the other profiles", func(t *testing.T) { dir := setupProfilesDir(t) require.NoError(t, os.MkdirAll(filepath.Join(dir, "alice"), 0o700))