Skip to content

Commit d1efbd8

Browse files
committed
fix(config): clone request in RoundTrip and guard clientVersion with RWMutex
Fixes two concurrent-use bugs: - versionTransport.RoundTrip mutated the caller's request in place, violating the http.RoundTripper contract; now clones with req.Clone(). - clientVersion was read/written without synchronisation; protected with a sync.RWMutex (SetClientVersion holds write lock, RoundTrip holds read lock). Also threads SnapshotShellOhMyZsh through Config→InstallState so PlanFromSnapshot only sets InstallOhMyZsh=true when the snapshot actually recorded it, fixing the unconditional Oh-My-Zsh restore bug.
1 parent 03fafa7 commit d1efbd8

3 files changed

Lines changed: 24 additions & 7 deletions

File tree

internal/cli/snapshot.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,7 @@ func buildImportConfig(edited *snapshot.Snapshot, dryRun bool) *config.Config {
766766
}
767767
}
768768

769+
cfg.SnapshotShellOhMyZsh = edited.Shell.OhMyZsh
769770
cfg.SnapshotShellTheme = edited.Shell.Theme
770771
cfg.SnapshotShellPlugins = edited.Shell.Plugins
771772

internal/config/config.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,29 @@ func checkUpgradeHint(resp *http.Response) {
3838
var isAllowedAPIURL = system.IsAllowedAPIURL
3939

4040
// clientVersion is set by the CLI at startup via SetClientVersion.
41-
var clientVersion = "dev"
41+
// Guarded by clientVersionMu; RoundTrip reads it concurrently via HTTP goroutines.
42+
var (
43+
clientVersion = "dev"
44+
clientVersionMu sync.RWMutex
45+
)
4246

4347
// SetClientVersion sets the version string sent in X-OpenBoot-Version headers.
44-
func SetClientVersion(v string) { clientVersion = v }
48+
func SetClientVersion(v string) {
49+
clientVersionMu.Lock()
50+
clientVersion = v
51+
clientVersionMu.Unlock()
52+
}
4553

4654
// versionTransport wraps http.DefaultTransport to inject the version header.
55+
// It clones the request before modification per http.RoundTripper contract.
4756
type versionTransport struct{ base http.RoundTripper }
4857

4958
func (t *versionTransport) RoundTrip(req *http.Request) (*http.Response, error) {
50-
req.Header.Set("X-OpenBoot-Version", clientVersion)
51-
return t.base.RoundTrip(req)
59+
r2 := req.Clone(req.Context())
60+
clientVersionMu.RLock()
61+
r2.Header.Set("X-OpenBoot-Version", clientVersion)
62+
clientVersionMu.RUnlock()
63+
return t.base.RoundTrip(r2)
5264
}
5365

5466
var remoteHTTPClient = &http.Client{
@@ -93,7 +105,8 @@ type Config struct {
93105
SnapshotGit *SnapshotGitConfig // from snapshot capture
94106
SnapshotMacOS []RemoteMacOSPref // from snapshot capture
95107
SnapshotDotfiles string // from snapshot capture
96-
SnapshotShellTheme string // from snapshot capture
108+
SnapshotShellOhMyZsh bool // from snapshot capture
109+
SnapshotShellTheme string // from snapshot capture
97110
SnapshotShellPlugins []string // from snapshot capture
98111
}
99112

@@ -127,6 +140,7 @@ type InstallState struct {
127140
SnapshotGit *SnapshotGitConfig
128141
SnapshotMacOS []RemoteMacOSPref
129142
SnapshotDotfiles string
143+
SnapshotShellOhMyZsh bool
130144
SnapshotShellTheme string
131145
SnapshotShellPlugins []string
132146
}
@@ -162,6 +176,7 @@ func (c *Config) ToInstallState() *InstallState {
162176
SnapshotGit: c.SnapshotGit,
163177
SnapshotMacOS: c.SnapshotMacOS,
164178
SnapshotDotfiles: c.SnapshotDotfiles,
179+
SnapshotShellOhMyZsh: c.SnapshotShellOhMyZsh,
165180
SnapshotShellTheme: c.SnapshotShellTheme,
166181
SnapshotShellPlugins: c.SnapshotShellPlugins,
167182
}
@@ -177,6 +192,7 @@ func (c *Config) ApplyState(s *InstallState) {
177192
c.SnapshotGit = s.SnapshotGit
178193
c.SnapshotMacOS = s.SnapshotMacOS
179194
c.SnapshotDotfiles = s.SnapshotDotfiles
195+
c.SnapshotShellOhMyZsh = s.SnapshotShellOhMyZsh
180196
c.SnapshotShellTheme = s.SnapshotShellTheme
181197
c.SnapshotShellPlugins = s.SnapshotShellPlugins
182198
}

internal/installer/plan.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,8 @@ func PlanFromSnapshot(opts *config.InstallOptions, st *config.InstallState) Inst
321321
plan.DotfilesURL = st.SnapshotDotfiles
322322
}
323323

324-
// Shell: restore Oh-My-Zsh and theme/plugins from snapshot unless explicitly skipped.
325-
if opts.Shell != "skip" {
324+
// Shell: restore exactly what the snapshot recorded; don't install OMZ if it wasn't there.
325+
if opts.Shell != "skip" && st.SnapshotShellOhMyZsh {
326326
plan.InstallOhMyZsh = true
327327
plan.ShellTheme = st.SnapshotShellTheme
328328
plan.ShellPlugins = st.SnapshotShellPlugins

0 commit comments

Comments
 (0)