Repository: open-telemetry/opentelemetry-collector-contrib (2025-06-06T13:12:57Z)

Issue:
[cmd/opampsupervisor] Reload Collector configuration with SIGHUP
### Component(s)

cmd/opampsupervisor

### Is your feature request related to a problem? Please describe.

Currently the Supervisor restarts the Collector by stop its process and starting again. 

### Describe the solution you'd like

I would like the Supervisor to use [the SIGHUP configuration reload feature baked in the Collector](https://github.com/open-telemetry/opentelemetry-collector/pull/6000) to delegate the configuration reload work to the Collector itself.



### Describe alternatives you've considered

- Use an HTTP endpoint to trigger the configuration reload on the Collector: not possible until https://github.com/open-telemetry/opentelemetry-collector/issues/10264 is completed.
- Hot reload of configuration: just not ready yet, as discussed in https://github.com/open-telemetry/opentelemetry-collector/issues/5966.

### Additional context

_No response_


Discussion:
Pinging code owners:
- cmd/opampsupervisor: @evan-bradley @atoulme @tigrannajaryan

 See [Adding Labels via Comments](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#adding-labels-via-comments) if you do not have permissions to add labels yourself.

Sounds good to me. Let's make the behavior configurable. 



Patch:
diff --git a/.chloggen/supervisor-hup-reload.yaml b/.chloggen/supervisor-hup-reload.yaml
new file mode 100644
index 0000000000000..841dda06a38c8
--- /dev/null
+++ b/.chloggen/supervisor-hup-reload.yaml
@@ -0,0 +1,27 @@
+# Use this changelog template to create an entry for release notes.
+
+# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
+change_type: enhancement
+
+# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
+component: cmd/opampsupervisor
+
+# A brief description of the change.  Surround your text with quotes ("") if it needs to start with a backtick (`).
+note: Allow the Supervisor send a SIGHUP signal to the agent to reload its configuration.
+
+# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
+issues: [40410]
+
+# (Optional) One or more lines of additional information to render under the primary note.
+# These lines will be padded with 2 spaces and then inserted directly into the document.
+# Use pipe (|) for multiline entries.
+subtext: This behavior is disabled by default. To enable it, set the `agent::enable_hup_reload` flag to `true` in the supervisor configuration.
+
+# If your change doesn't affect end users or the exported elements of any package,
+# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
+# Optional: The change log or logs in which this entry should be included.
+# e.g. '[user]' or '[user, api]'
+# Include 'user' if the change is relevant to end users.
+# Include 'api' if there is a change to a library API.
+# Default: '[user]'
+change_logs: []
diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go
index c9fce7b591398..43b438972aaae 100644
--- a/cmd/opampsupervisor/supervisor/commander/commander.go
+++ b/cmd/opampsupervisor/supervisor/commander/commander.go
@@ -13,6 +13,7 @@ import (
 	"path/filepath"
 	"slices"
 	"sync/atomic"
+	"syscall"
 	"time"
 
 	"go.uber.org/zap"
@@ -95,6 +96,19 @@ func (c *Commander) Restart(ctx context.Context) error {
 	return c.Start(ctx)
 }
 
+func (c *Commander) ReloadConfigFile() error {
+	if c.cmd == nil || c.cmd.Process == nil {
+		return errors.New("agent process is not running")
+	}
+
+	c.logger.Debug("Sending SIGHUP to agent process to reload config", zap.Int("pid", c.cmd.Process.Pid))
+	if err := c.cmd.Process.Signal(syscall.SIGHUP); err != nil {
+		return fmt.Errorf("failed to send SIGHUP to agent process: %w", err)
+	}
+
+	return nil
+}
+
 func (c *Commander) startNormal() error {
 	logFilePath := filepath.Join(c.logsDir, "agent.log")
 	stdoutFile, err := os.Create(logFilePath)
@@ -323,7 +337,7 @@ func (c *Commander) Stop(ctx context.Context) error {
 	}
 
 	pid := c.cmd.Process.Pid
-	c.logger.Debug("Stopping agent process", zap.Int("pid", pid))
+	c.logger.Debug("sending shutdown signal to agent process", zap.Int("pid", pid))
 
 	// Gracefully signal process to stop.
 	if err := sendShutdownSignal(c.cmd.Process); err != nil {
diff --git a/cmd/opampsupervisor/supervisor/config/config.go b/cmd/opampsupervisor/supervisor/config/config.go
index d88f1af574cf6..b44ec99b896df 100644
--- a/cmd/opampsupervisor/supervisor/config/config.go
+++ b/cmd/opampsupervisor/supervisor/config/config.go
@@ -187,6 +187,7 @@ type Agent struct {
 	BootstrapTimeout        time.Duration     `mapstructure:"bootstrap_timeout"`
 	OpAMPServerPort         int               `mapstructure:"opamp_server_port"`
 	PassthroughLogs         bool              `mapstructure:"passthrough_logs"`
+	UseHUPConfigReload      bool              `mapstructure:"use_hup_config_reload"`
 	ConfigFiles             []string          `mapstructure:"config_files"`
 	Arguments               []string          `mapstructure:"args"`
 	Env                     map[string]string `mapstructure:"env"`
diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go
index 89efcf4f0b0be..6f3384720072b 100644
--- a/cmd/opampsupervisor/supervisor/supervisor.go
+++ b/cmd/opampsupervisor/supervisor/supervisor.go
@@ -113,8 +113,6 @@ type Supervisor struct {
 	// Commander that starts/stops the Agent process.
 	commander *commander.Commander
 
-	startedAt time.Time
-
 	// Supervisor's own config.
 	config config.Supervisor
 
@@ -163,10 +161,12 @@ type Supervisor struct {
 	customMessageToServer chan *protobufs.CustomMessage
 	customMessageWG       sync.WaitGroup
 
-	// agentHasStarted is true if the agent has started.
-	agentHasStarted bool
-	// agentStartHealthCheckAttempts is the number of health check attempts made by the agent since it started.
-	agentStartHealthCheckAttempts int
+	// agentReady is true if the agent has started and is fully ready.
+	agentReady atomic.Bool
+	// agentReadyChan is a channel that can be used to wait for the agent to
+	// start in case [agentReady] is false.
+	agentReadyChan chan struct{}
+
 	// agentRestarting is true if the agent is restarting.
 	agentRestarting atomic.Bool
 
@@ -192,6 +192,8 @@ func NewSupervisor(logger *zap.Logger, cfg config.Supervisor) (*Supervisor, erro
 		customMessageToServer:        make(chan *protobufs.CustomMessage, maxBufferedCustomMessages),
 		agentConn:                    &atomic.Value{},
 		featureGates:                 map[string]struct{}{},
+		agentReady:                   atomic.Bool{},
+		agentReadyChan:               make(chan struct{}, 1),
 	}
 	if err := s.createTemplates(); err != nil {
 		return nil, err
@@ -777,6 +779,9 @@ func (s *Supervisor) handleAgentOpAMPMessage(conn serverTypes.Connection, messag
 		if err != nil {
 			s.telemetrySettings.Logger.Error("Could not report health to OpAMP server", zap.Error(err))
 		}
+		if !s.agentReady.Load() && message.Health.Healthy {
+			s.markAgentReady()
+		}
 	}
 
 	return &protobufs.ServerToAgent{}
@@ -1296,6 +1301,7 @@ func (s *Supervisor) handleRestartCommand() error {
 	if err != nil {
 		s.telemetrySettings.Logger.Error("Could not restart agent process", zap.Error(err))
 	}
+	s.resetAgentReady()
 	return err
 }
 
@@ -1322,10 +1328,6 @@ func (s *Supervisor) startAgent() (agentStartStatus, error) {
 		return "", startErr
 	}
 
-	s.agentHasStarted = false
-	s.agentStartHealthCheckAttempts = 0
-	s.startedAt = time.Now()
-
 	return agentStarting, nil
 }
 
@@ -1350,6 +1352,7 @@ func (s *Supervisor) runAgentProcess() {
 		select {
 		case <-s.hasNewConfig:
 			s.lastHealthFromClient.Store(nil)
+			s.telemetrySettings.Logger.Debug("agent has new config", zap.String("previous_health", s.lastHealthFromClient.Load().String()))
 			if !configApplyTimeoutTimer.Stop() {
 				select {
 				case <-configApplyTimeoutTimer.C: // Try to drain the channel
@@ -1357,18 +1360,30 @@ func (s *Supervisor) runAgentProcess() {
 				}
 			}
 			configApplyTimeoutTimer.Reset(s.config.Agent.ConfigApplyTimeout)
-
-			s.telemetrySettings.Logger.Debug("Restarting agent due to new config")
 			restartTimer.Stop()
-			s.stopAgentApplyConfig()
+
+			if s.config.Agent.UseHUPConfigReload {
+				if err := s.hupReloadAgent(); err != nil {
+					s.telemetrySettings.Logger.Error("Failed to HUP restart agent", zap.Error(err))
+					s.saveAndReportConfigStatus(protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED, err.Error())
+					continue
+				}
+			} else {
+				s.stopAgentApplyConfig()
+			}
+
+			s.telemetrySettings.Logger.Debug("Agent is not running, starting new instance")
+			// This call to [startAgent] is useful for both the normal config reload
+			// and the HUP one. It takes care of not starting the agent if the config
+			// is empty and also of starting it when the config changes from empty
+			// to non-empty.
 			status, err := s.startAgent()
 			if err != nil {
 				s.telemetrySettings.Logger.Error("starting agent with new config failed", zap.Error(err))
 				s.saveAndReportConfigStatus(protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED, err.Error())
 			}
-
 			if status == agentNotStarting {
-				// not starting agent because of nop config, clear timer, report applied status, report healthy status
+				// not starting agent because of nop config: clear timer, report applied status, report healthy status
 				configApplyTimeoutTimer.Stop()
 				s.saveAndReportConfigStatus(protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED, "")
 				if err := s.opampClient.SetHealth(&protobufs.ComponentHealth{Healthy: true, LastError: ""}); err != nil {
@@ -1384,7 +1399,6 @@ func (s *Supervisor) runAgentProcess() {
 					}
 				}
 			}
-
 		case <-s.commander.Exited():
 			// the agent process exit is expected for restart command and will not attempt to restart
 			if s.agentRestarting.Load() {
@@ -1439,16 +1453,116 @@ func (s *Supervisor) runAgentProcess() {
 	}
 }
 
+// markAgentReady marks the agent as ready and sends a signal to
+// [agentReadyChan].
+func (s *Supervisor) markAgentReady() {
+	s.agentReady.Store(true)
+	select {
+	case s.agentReadyChan <- struct{}{}:
+	default:
+	}
+}
+
+// resetAgentReady resets the agent as not ready and drains [agentReadyChan].
+func (s *Supervisor) resetAgentReady() {
+	s.agentReady.Store(false)
+	select {
+	case <-s.agentReadyChan:
+	default:
+	}
+}
+
+// waitForAgentReady waits for the agent to be ready. The agent is considered to
+// be ready when its first health report is received by the Supervisor's opamp
+// server.
+// WARNING: this is not thread-safe! If there are two goroutines waiting for
+// the agent to be ready, only one of them will be able to proceed.
+func (s *Supervisor) waitForAgentReady() error {
+	if s.agentReady.Load() {
+		return nil
+	}
+
+	bootstrapTimer := time.NewTimer(s.config.Agent.BootstrapTimeout)
+	defer bootstrapTimer.Stop()
+
+	select {
+	case <-s.agentReadyChan:
+		return nil
+	case <-bootstrapTimer.C:
+		return fmt.Errorf("agent has not started after %s", s.config.Agent.BootstrapTimeout)
+	}
+}
+
+// hupReloadAgent sends a HUP signal to the agent process  with the intent of
+// triggering a configuration reload. There are 3 possible outcomes of this:
+// 1. The agent is the "official" Otel Collector, which properly traps the HUP
+// signal and reloads the config.
+// 2. The agent is a custom process, which does not trap the HUP signal: in
+// this case the default behavior of a process that receives a HUP signal is to
+// exit. This ends up being the same behavior as the standard stop -> restart
+// configuration reload method.
+// 3. The agent traps the HUP signal, but does nothing. On the next health
+// report the agent will be consiered healthy and ready, even though it might
+// be running on old configuration.
+func (s *Supervisor) hupReloadAgent() error {
+	s.agentRestarting.Store(true)
+	defer s.agentRestarting.Store(false)
+	defer s.resetAgentReady()
+
+	// If we have an empty config, the agent should be stopped.
+	if s.cfgState.Load().(*configState).configMapIsEmpty {
+		s.stopAgentApplyConfig()
+		return nil
+	}
+
+	if err := s.writeAgentConfig(); err != nil {
+		return fmt.Errorf("failed to write agent new config: %w", err)
+	}
+
+	// If the agent is not running, we can't send a HUP signal to it, so we
+	// return and let it be started by the caller.
+	if !s.commander.IsRunning() {
+		return nil
+	}
+
+	if err := s.waitForAgentReady(); err != nil {
+		return err
+	}
+
+	s.telemetrySettings.Logger.Debug("agent is running, reloading config")
+	if err := s.reloadAgentConfig(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *Supervisor) reloadAgentConfig() error {
+	s.telemetrySettings.Logger.Debug("Reloading the agent config")
+	err := s.commander.ReloadConfigFile()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *Supervisor) writeAgentConfig() error {
+	cfgState := s.cfgState.Load().(*configState)
+	if err := os.WriteFile(s.agentConfigFilePath(), []byte(cfgState.mergedConfig), 0o600); err != nil {
+		return err
+	}
+	return nil
+}
+
 func (s *Supervisor) stopAgentApplyConfig() {
 	s.telemetrySettings.Logger.Debug("Stopping the agent to apply new config")
-	cfgState := s.cfgState.Load().(*configState)
 	err := s.commander.Stop(context.Background())
 	if err != nil {
 		s.telemetrySettings.Logger.Error("Could not stop agent process", zap.Error(err))
 	}
 
-	if err := os.WriteFile(s.agentConfigFilePath(), []byte(cfgState.mergedConfig), 0o600); err != nil {
-		s.telemetrySettings.Logger.Error("Failed to write agent config.", zap.Error(err))
+	if err := s.writeAgentConfig(); err != nil {
+		s.telemetrySettings.Logger.Error("Failed to write agent config", zap.Error(err))
 	}
 }
 


Test patch:
diff --git a/cmd/opampsupervisor/e2e_test.go b/cmd/opampsupervisor/e2e_test.go
index 20b40b63cfe84..1435c2efb3173 100644
--- a/cmd/opampsupervisor/e2e_test.go
+++ b/cmd/opampsupervisor/e2e_test.go
@@ -169,7 +169,7 @@ func newUnstartedOpAMPServer(t *testing.T, connectingCallback onConnectingFuncFa
 	}
 }
 
-func newSupervisor(t *testing.T, configType string, extraConfigData map[string]string) *supervisor.Supervisor {
+func newSupervisor(t *testing.T, configType string, extraConfigData map[string]string) (*supervisor.Supervisor, *config.Supervisor) {
 	cfgFile := getSupervisorConfig(t, configType, extraConfigData)
 
 	cfg, err := config.Load(cfgFile.Name())
@@ -181,7 +181,7 @@ func newSupervisor(t *testing.T, configType string, extraConfigData map[string]s
 	s, err := supervisor.NewSupervisor(logger, cfg)
 	require.NoError(t, err)
 
-	return s
+	return s, &cfg
 }
 
 func getSupervisorConfig(t *testing.T, configType string, extraConfigData map[string]string) *os.File {
@@ -220,220 +220,325 @@ func getSupervisorConfig(t *testing.T, configType string, extraConfigData map[st
 }
 
 func TestSupervisorStartsCollectorWithRemoteConfig(t *testing.T) {
-	var agentConfig atomic.Value
-	server := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPReload",
+			UseHUPConfigReload: true,
+		},
+	}
 
-				return &protobufs.ServerToAgent{}
-			},
-		})
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			storageDir := t.TempDir()
+			var agentConfig atomic.Value
+			server := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
 
-	s := newSupervisor(t, "basic", map[string]string{"url": server.addr})
+						return &protobufs.ServerToAgent{}
+					},
+				})
 
-	require.Nil(t, s.Start())
-	defer s.Shutdown()
+			extraConfigData := map[string]string{"url": server.addr, "storage_dir": storageDir}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			s, supervisorCfg := newSupervisor(t, "basic", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	cfg, hash, inputFile, outputFile := createSimplePipelineCollectorConf(t)
+			require.Nil(t, s.Start())
+			defer s.Shutdown()
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: cfg.Bytes()},
+			waitForSupervisorConnection(server.supervisorConnected, true)
+
+			cfg, hash, inputFile, outputFile := createSimplePipelineCollectorConf(t)
+
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
 				},
-			},
-			ConfigHash: hash,
-		},
-	})
+			})
 
-	require.Eventually(t, func() bool {
-		cfg, ok := agentConfig.Load().(string)
-		if ok {
-			// The effective config may be structurally different compared to what was sent,
-			// and will also have some data redacted,
-			// so just check that it includes the filelog receiver
-			return strings.Contains(cfg, "filelog")
-		}
+			require.Eventually(t, func() bool {
+				cfg, ok := agentConfig.Load().(string)
+				if ok {
+					// The effective config may be structurally different compared to what was sent,
+					// and will also have some data redacted,
+					// so just check that it includes the filelog receiver
+					return strings.Contains(cfg, "filelog")
+				}
 
-		return false
-	}, 5*time.Second, 500*time.Millisecond, "Collector was not started with remote config")
+				return false
+			}, 5*time.Second, 500*time.Millisecond, "Collector was not started with remote config")
 
-	n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
-	require.NotZero(t, n, "Could not write to input file")
-	require.NoError(t, err)
+			n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
+			require.NotZero(t, n, "Could not write to input file")
+			require.NoError(t, err)
 
-	require.Eventually(t, func() bool {
-		logRecord := make([]byte, 1024)
-		n, _ := outputFile.Read(logRecord)
+			require.Eventually(t, func() bool {
+				logRecord := make([]byte, 1024)
+				n, _ := outputFile.Read(logRecord)
 
-		return n != 0
-	}, 10*time.Second, 500*time.Millisecond, "Log never appeared in output")
+				return n != 0
+			}, 10*time.Second, 500*time.Millisecond, "Log never appeared in output")
+		})
+	}
 }
 
 func TestSupervisorStartsCollectorWithLocalConfigOnly(t *testing.T) {
-	connected := atomic.Bool{}
-	server := newOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
-		OnConnected: func(_ context.Context, _ types.Connection) {
-			connected.Store(true)
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
 		},
-	})
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-	cfg, _, inputFile, outputFile := createSimplePipelineCollectorConf(t)
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			connected := atomic.Bool{}
+			server := newOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
+				OnConnected: func(_ context.Context, _ types.Connection) {
+					connected.Store(true)
+				},
+			})
 
-	collectorConfigDir := t.TempDir()
-	cfgFile, err := os.CreateTemp(collectorConfigDir, "config_*.yaml")
-	t.Cleanup(func() { cfgFile.Close() })
-	require.NoError(t, err)
+			cfg, _, inputFile, outputFile := createSimplePipelineCollectorConf(t)
 
-	_, err = cfgFile.Write(cfg.Bytes())
-	require.NoError(t, err)
+			collectorConfigDir := t.TempDir()
+			cfgFile, err := os.CreateTemp(collectorConfigDir, "config_*.yaml")
+			t.Cleanup(func() { cfgFile.Close() })
+			require.NoError(t, err)
 
-	storageDir := t.TempDir()
+			_, err = cfgFile.Write(cfg.Bytes())
+			require.NoError(t, err)
 
-	s := newSupervisor(t, "basic", map[string]string{
-		"url":          server.addr,
-		"storage_dir":  storageDir,
-		"local_config": cfgFile.Name(),
-	})
-	t.Cleanup(s.Shutdown)
-	require.NoError(t, s.Start())
+			storageDir := t.TempDir()
 
-	waitForSupervisorConnection(server.supervisorConnected, true)
-	require.True(t, connected.Load(), "Supervisor failed to connect")
+			extraConfigData := map[string]string{
+				"url":          server.addr,
+				"storage_dir":  storageDir,
+				"local_config": cfgFile.Name(),
+			}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	require.EventuallyWithTf(t, func(c *assert.CollectT) {
-		require.Contains(c, getAgentLogs(t, storageDir), "Connected to the OpAMP server")
-	}, 10*time.Second, 500*time.Millisecond, "Collector did not connected to the OpAMP server")
+			s, supervisorCfg := newSupervisor(t, "basic", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
+			t.Cleanup(s.Shutdown)
+			require.NoError(t, s.Start())
 
-	n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
-	require.NotZero(t, n, "Could not write to input file")
-	require.NoError(t, err)
+			waitForSupervisorConnection(server.supervisorConnected, true)
+			require.True(t, connected.Load(), "Supervisor failed to connect")
 
-	require.Eventually(t, func() bool {
-		logRecord := make([]byte, 1024)
-		n, _ := outputFile.Read(logRecord)
+			require.EventuallyWithTf(t, func(c *assert.CollectT) {
+				require.Contains(c, getAgentLogs(t, storageDir), "Connected to the OpAMP server")
+			}, 10*time.Second, 500*time.Millisecond, "Collector did not connected to the OpAMP server")
 
-		return n != 0
-	}, 10*time.Second, 500*time.Millisecond, "Log never appeared in output")
+			n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
+			require.NotZero(t, n, "Could not write to input file")
+			require.NoError(t, err)
+
+			require.Eventually(t, func() bool {
+				logRecord := make([]byte, 1024)
+				n, _ := outputFile.Read(logRecord)
+
+				return n != 0
+			}, 10*time.Second, 500*time.Millisecond, "Log never appeared in output")
+		})
+	}
 }
 
 func TestSupervisorStartsCollectorWithNoOpAMPServerWithNoLastRemoteConfig(t *testing.T) {
-	storageDir := t.TempDir()
-	t.Log("Storage dir:", storageDir)
-	t.Cleanup(func() {
-		content, _ := os.ReadFile(filepath.Join(storageDir, "effective_config.yaml"))
-		t.Logf("EffectiveConfig:\n%s", string(content))
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-		content, _ = os.ReadFile(filepath.Join(storageDir, "agent.log"))
-		t.Logf("Agent logs:\n%s", string(content))
-	})
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			storageDir := t.TempDir()
+			t.Log("Storage dir:", storageDir)
+			t.Cleanup(func() {
+				content, _ := os.ReadFile(filepath.Join(storageDir, "effective_config.yaml"))
+				t.Logf("EffectiveConfig:\n%s", string(content))
 
-	connected := atomic.Bool{}
-	server := newUnstartedOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
-		OnConnected: func(ctx context.Context, conn types.Connection) {
-			connected.Store(true)
-		},
-	})
+				content, _ = os.ReadFile(filepath.Join(storageDir, "agent.log"))
+				t.Logf("Agent logs:\n%s", string(content))
+			})
 
-	s := newSupervisor(t, "healthcheck_port", map[string]string{
-		"url":          server.addr,
-		"storage_dir":  storageDir,
-		"local_config": filepath.Join("testdata", "collector", "healthcheck_config.yaml"),
-	})
-	t.Cleanup(s.Shutdown)
-	require.Nil(t, s.Start())
+			connected := atomic.Bool{}
+			server := newUnstartedOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
+				OnConnected: func(ctx context.Context, conn types.Connection) {
+					connected.Store(true)
+				},
+			})
 
-	// Verify the collector runs eventually by pinging the healthcheck extension
-	require.Eventually(t, func() bool {
-		resp, err := http.DefaultClient.Get("http://localhost:13133")
-		if err != nil {
-			t.Logf("Failed healthcheck: %s", err)
-			return false
-		}
-		require.NoError(t, resp.Body.Close())
-		if resp.StatusCode >= 300 || resp.StatusCode < 200 {
-			t.Logf("Got non-2xx status code: %d", resp.StatusCode)
-			return false
-		}
-		return true
-	}, 3*time.Second, 100*time.Millisecond)
+			extraConfigData := map[string]string{
+				"url":          server.addr,
+				"storage_dir":  storageDir,
+				"local_config": filepath.Join("testdata", "collector", "healthcheck_config.yaml"),
+			}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	// Start the server and wait for the supervisor to connect
-	server.start()
+			s, supervisorCfg := newSupervisor(t, "healthcheck_port", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
+			t.Cleanup(s.Shutdown)
+			require.Nil(t, s.Start())
 
-	// Verify supervisor connects to server
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			// Verify the collector runs eventually by pinging the healthcheck extension
+			require.Eventually(t, func() bool {
+				resp, err := http.DefaultClient.Get("http://localhost:13133")
+				if err != nil {
+					t.Logf("Failed healthcheck: %s", err)
+					return false
+				}
+				require.NoError(t, resp.Body.Close())
+				if resp.StatusCode >= 300 || resp.StatusCode < 200 {
+					t.Logf("Got non-2xx status code: %d", resp.StatusCode)
+					return false
+				}
+				return true
+			}, 3*time.Second, 100*time.Millisecond)
 
-	require.True(t, connected.Load(), "Supervisor failed to connect")
+			// Start the server and wait for the supervisor to connect
+			server.start()
+
+			// Verify supervisor connects to server
+			waitForSupervisorConnection(server.supervisorConnected, true)
+
+			require.True(t, connected.Load(), "Supervisor failed to connect")
+		})
+	}
 }
 
 func TestSupervisorStartsCollectorWithNoOpAMPServerUsingLastRemoteConfig(t *testing.T) {
-	storageDir := t.TempDir()
-	remoteConfigFilePath := filepath.Join(storageDir, "last_recv_remote_config.dat")
-
-	cfg, hash, healthcheckPort := createHealthCheckCollectorConf(t)
-	remoteConfigProto := &protobufs.AgentRemoteConfig{
-		Config: &protobufs.AgentConfigMap{
-			ConfigMap: map[string]*protobufs.AgentConfigFile{
-				"": {Body: cfg.Bytes()},
-			},
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
 		},
-		ConfigHash: hash,
 	}
-	marshalledRemoteConfig, err := proto.Marshal(remoteConfigProto)
-	require.NoError(t, err)
 
-	require.NoError(t, os.WriteFile(remoteConfigFilePath, marshalledRemoteConfig, 0o600))
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			storageDir := t.TempDir()
+			remoteConfigFilePath := filepath.Join(storageDir, "last_recv_remote_config.dat")
 
-	connected := atomic.Bool{}
-	server := newUnstartedOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
-		OnConnected: func(ctx context.Context, conn types.Connection) {
-			connected.Store(true)
-		},
-	})
-	defer server.shutdown()
+			cfg, hash, healthcheckPort := createHealthCheckCollectorConf(t)
+			remoteConfigProto := &protobufs.AgentRemoteConfig{
+				Config: &protobufs.AgentConfigMap{
+					ConfigMap: map[string]*protobufs.AgentConfigFile{
+						"": {Body: cfg.Bytes()},
+					},
+				},
+				ConfigHash: hash,
+			}
+			marshalledRemoteConfig, err := proto.Marshal(remoteConfigProto)
+			require.NoError(t, err)
 
-	s := newSupervisor(t, "basic", map[string]string{
-		"url":         server.addr,
-		"storage_dir": storageDir,
-	})
+			require.NoError(t, os.WriteFile(remoteConfigFilePath, marshalledRemoteConfig, 0o600))
 
-	require.Nil(t, s.Start())
-	defer s.Shutdown()
+			connected := atomic.Bool{}
+			server := newUnstartedOpAMPServer(t, defaultConnectingHandler, types.ConnectionCallbacks{
+				OnConnected: func(ctx context.Context, conn types.Connection) {
+					connected.Store(true)
+				},
+			})
+			defer server.shutdown()
 
-	// Verify the collector runs eventually by pinging the healthcheck extension
-	require.Eventually(t, func() bool {
-		resp, err := http.DefaultClient.Get(fmt.Sprintf("http://localhost:%d", healthcheckPort))
-		if err != nil {
-			t.Logf("Failed healthcheck: %s", err)
-			return false
-		}
-		require.NoError(t, resp.Body.Close())
-		if resp.StatusCode >= 300 || resp.StatusCode < 200 {
-			t.Logf("Got non-2xx status code: %d", resp.StatusCode)
-			return false
-		}
-		return true
-	}, 3*time.Second, 100*time.Millisecond)
+			extraConfigData := map[string]string{
+				"url":         server.addr,
+				"storage_dir": storageDir,
+			}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	// Start the server and wait for the supervisor to connect
-	server.start()
+			s, supervisorCfg := newSupervisor(t, "basic", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	// Verify supervisor connects to server
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			require.Nil(t, s.Start())
+			defer s.Shutdown()
 
-	require.True(t, connected.Load(), "Supervisor failed to connect")
+			// Verify the collector runs eventually by pinging the healthcheck extension
+			require.Eventually(t, func() bool {
+				resp, err := http.DefaultClient.Get(fmt.Sprintf("http://localhost:%d", healthcheckPort))
+				if err != nil {
+					t.Logf("Failed healthcheck: %s", err)
+					return false
+				}
+				require.NoError(t, resp.Body.Close())
+				if resp.StatusCode >= 300 || resp.StatusCode < 200 {
+					t.Logf("Got non-2xx status code: %d", resp.StatusCode)
+					return false
+				}
+				return true
+			}, 3*time.Second, 100*time.Millisecond)
+
+			// Start the server and wait for the supervisor to connect
+			server.start()
+
+			// Verify supervisor connects to server
+			waitForSupervisorConnection(server.supervisorConnected, true)
+
+			require.True(t, connected.Load(), "Supervisor failed to connect")
+		})
+	}
 }
 
 func TestSupervisorStartsCollectorWithRemoteConfigAndExecParams(t *testing.T) {
@@ -485,7 +590,7 @@ func TestSupervisorStartsCollectorWithRemoteConfigAndExecParams(t *testing.T) {
 	require.NoError(t, err)
 
 	// fill env variables passed via parameters which are used in the collector config passed via config_files param
-	s := newSupervisor(t, "exec_config", map[string]string{
+	s, _ := newSupervisor(t, "exec_config", map[string]string{
 		"url":             server.addr,
 		"storage_dir":     storageDir,
 		"inputLogFile":    inputFile.Name(),
@@ -550,7 +655,7 @@ func TestSupervisorStartsWithNoOpAMPServer(t *testing.T) {
 
 	// The supervisor is started without a running OpAMP server.
 	// The supervisor should start successfully, even if the OpAMP server is stopped.
-	s := newSupervisor(t, "healthcheck_port", map[string]string{
+	s, _ := newSupervisor(t, "healthcheck_port", map[string]string{
 		"url":              server.addr,
 		"healthcheck_port": "12345",
 	})
@@ -612,90 +717,116 @@ func TestSupervisorStartsWithNoOpAMPServer(t *testing.T) {
 }
 
 func TestSupervisorRestartsCollectorAfterBadConfig(t *testing.T) {
-	var healthReport atomic.Value
-	var agentConfig atomic.Value
-	server := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.Health != nil {
-					healthReport.Store(message.Health)
-				}
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-				return &protobufs.ServerToAgent{}
-			},
-		})
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			var healthReport atomic.Value
+			var agentConfig atomic.Value
+			server := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.Health != nil {
+							healthReport.Store(message.Health)
+						}
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
 
-	s := newSupervisor(t, "basic", map[string]string{"url": server.addr})
+						return &protobufs.ServerToAgent{}
+					},
+				})
 
-	require.Nil(t, s.Start())
-	defer s.Shutdown()
+			extraConfigData := map[string]string{"url": server.addr}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			s, supervisorCfg := newSupervisor(t, "basic", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	cfg, hash := createBadCollectorConf(t)
+			require.Nil(t, s.Start())
+			defer s.Shutdown()
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: cfg.Bytes()},
+			waitForSupervisorConnection(server.supervisorConnected, true)
+
+			cfg, hash := createBadCollectorConf(t)
+
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
 				},
-			},
-			ConfigHash: hash,
-		},
-	})
+			})
 
-	require.Eventually(t, func() bool {
-		cfg, ok := agentConfig.Load().(string)
-		if ok {
-			// The effective config may be structurally different compared to what was sent,
-			// so just check that it includes some strings we know to be unique to the remote config.
-			return strings.Contains(cfg, "doesntexist")
-		}
+			require.Eventually(t, func() bool {
+				cfg, ok := agentConfig.Load().(string)
+				if ok {
+					// The effective config may be structurally different compared to what was sent,
+					// so just check that it includes some strings we know to be unique to the remote config.
+					return strings.Contains(cfg, "doesntexist")
+				}
 
-		return false
-	}, 5*time.Second, 500*time.Millisecond, "Collector was not started with remote config")
+				return false
+			}, 5*time.Second, 500*time.Millisecond, "Collector was not started with remote config")
 
-	require.Eventually(t, func() bool {
-		health := healthReport.Load().(*protobufs.ComponentHealth)
+			require.Eventually(t, func() bool {
+				health := healthReport.Load().(*protobufs.ComponentHealth)
 
-		if health != nil {
-			return !health.Healthy && health.LastError != ""
-		}
+				if health != nil {
+					return !health.Healthy && health.LastError != ""
+				}
 
-		return false
-	}, 5*time.Second, 250*time.Millisecond, "Supervisor never reported that the Collector was unhealthy")
+				return false
+			}, 5*time.Second, 250*time.Millisecond, "Supervisor never reported that the Collector was unhealthy")
 
-	cfg, hash, _, _ = createSimplePipelineCollectorConf(t)
+			cfg, hash, _, _ = createSimplePipelineCollectorConf(t)
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: cfg.Bytes()},
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
 				},
-			},
-			ConfigHash: hash,
-		},
-	})
+			})
 
-	require.Eventually(t, func() bool {
-		health := healthReport.Load().(*protobufs.ComponentHealth)
+			require.Eventually(t, func() bool {
+				health := healthReport.Load().(*protobufs.ComponentHealth)
 
-		if health != nil {
-			return health.Healthy && health.LastError == ""
-		}
+				if health != nil {
+					return health.Healthy && health.LastError == ""
+				}
 
-		return false
-	}, 5*time.Second, 250*time.Millisecond, "Supervisor never reported that the Collector became healthy")
+				return false
+			}, 5*time.Second, 250*time.Millisecond, "Supervisor never reported that the Collector became healthy")
+		})
+	}
 }
 
 func TestSupervisorConfiguresCapabilities(t *testing.T) {
@@ -711,7 +842,7 @@ func TestSupervisorConfiguresCapabilities(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "nocap", map[string]string{"url": server.addr})
+	s, _ := newSupervisor(t, "nocap", map[string]string{"url": server.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -801,7 +932,7 @@ func TestSupervisorBootstrapsCollector(t *testing.T) {
 					},
 				})
 
-			s := newSupervisor(t, "nocap", map[string]string{"url": server.addr})
+			s, _ := newSupervisor(t, "nocap", map[string]string{"url": server.addr})
 
 			require.Nil(t, s.Start())
 			defer s.Shutdown()
@@ -887,7 +1018,7 @@ func TestSupervisorBootstrapsCollectorAvailableComponents(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "reports_available_components", map[string]string{"url": server.addr})
+	s, _ := newSupervisor(t, "reports_available_components", map[string]string{"url": server.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -950,7 +1081,7 @@ func TestSupervisorReportsEffectiveConfig(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "basic", map[string]string{"url": server.addr})
+	s, _ := newSupervisor(t, "basic", map[string]string{"url": server.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -1061,7 +1192,7 @@ func TestSupervisorAgentDescriptionConfigApplies(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "agent_description", map[string]string{"url": server.addr})
+	s, _ := newSupervisor(t, "agent_description", map[string]string{"url": server.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -1192,93 +1323,123 @@ func waitForSupervisorConnection(connection chan bool, connected bool) {
 }
 
 func TestSupervisorRestartCommand(t *testing.T) {
-	var healthReport atomic.Value
-	var agentConfig atomic.Value
-	server := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.Health != nil {
-					healthReport.Store(message.Health)
-				}
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
-				return &protobufs.ServerToAgent{}
-			},
-		})
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			var healthReport atomic.Value
+			var agentConfig atomic.Value
+			server := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.Health != nil {
+							healthReport.Store(message.Health)
+						}
 
-	s := newSupervisor(t, "basic", map[string]string{"url": server.addr})
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
+						return &protobufs.ServerToAgent{}
+					},
+				})
 
-	require.Nil(t, s.Start())
-	defer s.Shutdown()
+			storageDir := t.TempDir()
+			extraConfigData := map[string]string{
+				"url":         server.addr,
+				"storage_dir": storageDir,
+			}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			s, supervisorCfg := newSupervisor(t, "basic", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	// Send the initial config
-	cfg, hash, _, _ := createSimplePipelineCollectorConf(t)
+			require.Nil(t, s.Start())
+			defer s.Shutdown()
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: cfg.Bytes()},
+			waitForSupervisorConnection(server.supervisorConnected, true)
+
+			// Send the initial config
+			cfg, hash, _, _ := createSimplePipelineCollectorConf(t)
+
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
 				},
-			},
-			ConfigHash: hash,
-		},
-	})
+			})
 
-	require.Eventually(t, func() bool {
-		cfg, ok := agentConfig.Load().(string)
-		if ok {
-			return strings.Contains(cfg, "health_check")
-		}
-		return false
-	}, 5*time.Second, 500*time.Millisecond, "Collector was not started with healthcheck")
+			require.Eventually(t, func() bool {
+				cfg, ok := agentConfig.Load().(string)
+				if ok {
+					return strings.Contains(cfg, "health_check")
+				}
+				return false
+			}, 5*time.Second, 500*time.Millisecond, "Collector was not started with healthcheck")
 
-	require.Eventually(t, func() bool {
-		health := healthReport.Load().(*protobufs.ComponentHealth)
+			require.Eventually(t, func() bool {
+				health := healthReport.Load().(*protobufs.ComponentHealth)
 
-		if health != nil {
-			return health.Healthy && health.LastError == ""
-		}
+				if health != nil {
+					return health.Healthy && health.LastError == ""
+				}
 
-		return false
-	}, 5*time.Second, 500*time.Millisecond, "Collector never became healthy")
+				return false
+			}, 5*time.Second, 500*time.Millisecond, "Collector never became healthy")
 
-	// The health report should be received after the restart
-	healthReport.Store(&protobufs.ComponentHealth{})
+			// The health report should be received after the restart
+			healthReport.Store(&protobufs.ComponentHealth{})
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		Command: &protobufs.ServerToAgentCommand{
-			Type: protobufs.CommandType_CommandType_Restart,
-		},
-	})
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				Command: &protobufs.ServerToAgentCommand{
+					Type: protobufs.CommandType_CommandType_Restart,
+				},
+			})
 
-	// Here we will wait for the supervisor connection to go away and come back.
-	// This helps us check that the restart logic is actually restarting the agent.
-	// Note: this also helps the data race detector confirm that access to the
-	// [Supervisor.lastHealthFromClient] has to be synchronized to prevent races.
-	waitForSupervisorConnection(server.supervisorConnected, false)
-	waitForSupervisorConnection(server.supervisorConnected, true)
+			// Here we will wait for the supervisor connection to go away and come back.
+			// This helps us check that the restart logic is actually restarting the agent.
+			// Note: this also helps the data race detector confirm that access to the
+			// [Supervisor.lastHealthFromClient] has to be synchronized to prevent races.
+			waitForSupervisorConnection(server.supervisorConnected, false)
+			waitForSupervisorConnection(server.supervisorConnected, true)
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		Flags: uint64(protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState),
-	})
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				Flags: uint64(protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState),
+			})
 
-	require.Eventually(t, func() bool {
-		health := healthReport.Load().(*protobufs.ComponentHealth)
-		if health != nil {
-			return health.Healthy && health.LastError == ""
-		}
-		return false
-	}, 30*time.Second, 250*time.Millisecond, "Collector never reported healthy after restart")
+			require.Eventually(t, func() bool {
+				health := healthReport.Load().(*protobufs.ComponentHealth)
+				if health != nil {
+					return health.Healthy && health.LastError == ""
+				}
+				return false
+			}, 30*time.Second, 250*time.Millisecond, "Collector never reported healthy after restart")
+		})
+	}
 }
 
 func TestSupervisorOpAMPConnectionSettings(t *testing.T) {
@@ -1288,7 +1449,7 @@ func TestSupervisorOpAMPConnectionSettings(t *testing.T) {
 		defaultConnectingHandler,
 		types.ConnectionCallbacks{})
 
-	s := newSupervisor(t, "accepts_conn", map[string]string{"url": initialServer.addr})
+	s, _ := newSupervisor(t, "accepts_conn", map[string]string{"url": initialServer.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -1340,7 +1501,7 @@ func TestSupervisorOpAMPWithHTTPEndpoint(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "http", map[string]string{"url": initialServer.addr})
+	s, _ := newSupervisor(t, "http", map[string]string{"url": initialServer.addr})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -1350,110 +1511,124 @@ func TestSupervisorOpAMPWithHTTPEndpoint(t *testing.T) {
 }
 
 func TestSupervisorRestartsWithLastReceivedConfig(t *testing.T) {
-	// Create a temporary directory to store the test config file.
-	tempDir := t.TempDir()
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
+		},
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-	var agentConfig atomic.Value
-	var initialRemoteConfigStatus atomic.Value
-	initialServer := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
-				if message.RemoteConfigStatus != nil {
-					initialRemoteConfigStatus.Store(message.RemoteConfigStatus)
-				}
-				return &protobufs.ServerToAgent{}
-			},
-		})
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			// Create a temporary directory to store the test config file.
+			tempDir := t.TempDir()
 
-	s := newSupervisor(t, "persistence", map[string]string{"url": initialServer.addr, "storage_dir": tempDir})
+			var agentConfig atomic.Value
+			var initialRemoteConfigStatus atomic.Value
+			initialServer := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
+						if message.RemoteConfigStatus != nil {
+							initialRemoteConfigStatus.Store(message.RemoteConfigStatus)
+						}
+						return &protobufs.ServerToAgent{}
+					},
+				})
 
-	require.Nil(t, s.Start())
+			extraConfigData := map[string]string{"url": initialServer.addr, "storage_dir": tempDir}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	waitForSupervisorConnection(initialServer.supervisorConnected, true)
+			s, supervisorCfg := newSupervisor(t, "persistence", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	simplePipelineCFG, simplePipelineHash, _, _ := createSimplePipelineCollectorConf(t)
+			require.Nil(t, s.Start())
 
-	initialServer.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: simplePipelineCFG.Bytes()},
-				},
-			},
-			ConfigHash: simplePipelineHash,
-		},
-	})
+			waitForSupervisorConnection(initialServer.supervisorConnected, true)
 
-	require.Eventually(t, func() bool {
-		// Check if the config file was written to the storage directory
-		_, err := os.Stat(path.Join(tempDir, "last_recv_remote_config.dat"))
-		return err == nil
-	}, 5*time.Second, 250*time.Millisecond, "Config file was not written to persistent storage directory")
+			cfg, hash, _, _ := createSimplePipelineCollectorConf(t)
 
-	// wait for remote config status to be applied
-	require.Eventually(t, func() bool {
-		status := initialRemoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return status != nil && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED && bytes.Equal(status.LastRemoteConfigHash, simplePipelineHash)
-	}, 20*time.Second, 500*time.Millisecond, "Initial remote config never became applied")
+			initialServer.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
+				},
+			})
 
-	agentConfig.Store("")
-	s.Shutdown()
-	initialServer.shutdown()
+			require.Eventually(t, func() bool {
+				// Check if the config file was written to the storage directory
+				_, err := os.Stat(path.Join(tempDir, "last_recv_remote_config.dat"))
+				return err == nil
+			}, 5*time.Second, 250*time.Millisecond, "Config file was not written to persistent storage directory")
 
-	var remoteConfigStatus atomic.Value
-	newServer := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
-				if message.RemoteConfigStatus != nil {
-					remoteConfigStatus.Store(message.RemoteConfigStatus)
-				}
-				return &protobufs.ServerToAgent{}
-			},
-		})
-	defer newServer.shutdown()
+			agentConfig.Store("")
+			s.Shutdown()
+			initialServer.shutdown()
 
-	s1 := newSupervisor(t, "persistence", map[string]string{"url": newServer.addr, "storage_dir": tempDir})
+			newServer := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
+						return &protobufs.ServerToAgent{}
+					},
+				})
+			defer newServer.shutdown()
 
-	require.Nil(t, s1.Start())
-	defer s1.Shutdown()
+			extraConfigData["url"] = newServer.addr
+			s1, supervisorCfg := newSupervisor(t, "persistence", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
 
-	waitForSupervisorConnection(newServer.supervisorConnected, true)
+			require.Nil(t, s1.Start())
+			defer s1.Shutdown()
 
-	newServer.sendToSupervisor(&protobufs.ServerToAgent{
-		Flags: uint64(protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState),
-	})
+			waitForSupervisorConnection(newServer.supervisorConnected, true)
 
-	// verify that the supervisor reports the existing remote config status
-	require.Eventually(t, func() bool {
-		status := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return status != nil && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED && bytes.Equal(status.LastRemoteConfigHash, simplePipelineHash)
-	}, 20*time.Second, 500*time.Millisecond, "Remote config status never became applied")
+			newServer.sendToSupervisor(&protobufs.ServerToAgent{
+				Flags: uint64(protobufs.ServerToAgentFlags_ServerToAgentFlags_ReportFullState),
+			})
 
-	// Check that the new Supervisor instance starts with the configuration from the last received remote config
-	require.Eventually(t, func() bool {
-		loadedConfig, ok := agentConfig.Load().(string)
-		if !ok {
-			return false
-		}
+			// Check that the new Supervisor instance starts with the configuration from the last received remote config
+			require.Eventually(t, func() bool {
+				loadedConfig, ok := agentConfig.Load().(string)
+				if !ok {
+					return false
+				}
 
-		return strings.Contains(loadedConfig, "filelog")
-	}, 10*time.Second, 500*time.Millisecond, "Collector was not started with the last received remote config")
+				return strings.Contains(loadedConfig, "filelog")
+			}, 10*time.Second, 500*time.Millisecond, "Collector was not started with the last received remote config")
+		})
+	}
 }
 
 func TestSupervisorPersistsInstanceID(t *testing.T) {
@@ -1476,7 +1651,7 @@ func TestSupervisorPersistsInstanceID(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "basic", map[string]string{
+	s, _ := newSupervisor(t, "basic", map[string]string{
 		"url":         server.addr,
 		"storage_dir": storageDir,
 	})
@@ -1508,7 +1683,7 @@ func TestSupervisorPersistsInstanceID(t *testing.T) {
 	default:
 	}
 
-	s = newSupervisor(t, "basic", map[string]string{
+	s, _ = newSupervisor(t, "basic", map[string]string{
 		"url":         server.addr,
 		"storage_dir": storageDir,
 	})
@@ -1561,7 +1736,7 @@ func TestSupervisorPersistsNewInstanceID(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "basic", map[string]string{
+	s, _ := newSupervisor(t, "basic", map[string]string{
 		"url":         server.addr,
 		"storage_dir": storageDir,
 	})
@@ -1591,7 +1766,7 @@ func TestSupervisorPersistsNewInstanceID(t *testing.T) {
 	default:
 	}
 
-	s = newSupervisor(t, "basic", map[string]string{
+	s, _ = newSupervisor(t, "basic", map[string]string{
 		"url":         server.addr,
 		"storage_dir": storageDir,
 	})
@@ -1623,7 +1798,7 @@ func TestSupervisorWritesAgentFilesToStorageDir(t *testing.T) {
 		types.ConnectionCallbacks{},
 	)
 
-	s := newSupervisor(t, "basic", map[string]string{
+	s, _ := newSupervisor(t, "basic", map[string]string{
 		"url":         server.addr,
 		"storage_dir": storageDir,
 	})
@@ -1671,7 +1846,7 @@ func TestSupervisorStopsAgentProcessWithEmptyConfigMap(t *testing.T) {
 			},
 		})
 
-	s := newSupervisor(t, "healthcheck_port", map[string]string{
+	s, _ := newSupervisor(t, "healthcheck_port", map[string]string{
 		"url": server.addr,
 	})
 
@@ -1839,142 +2014,171 @@ func TestSupervisorLogging(t *testing.T) {
 }
 
 func TestSupervisorRemoteConfigApplyStatus(t *testing.T) {
-	var agentConfig atomic.Value
-	var healthReport atomic.Value
-	var remoteConfigStatus atomic.Value
-	server := newOpAMPServer(
-		t,
-		defaultConnectingHandler,
-		types.ConnectionCallbacks{
-			OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
-				if message.EffectiveConfig != nil {
-					config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
-					if config != nil {
-						agentConfig.Store(string(config.Body))
-					}
-				}
-				if message.Health != nil {
-					healthReport.Store(message.Health)
-				}
-				if message.RemoteConfigStatus != nil {
-					remoteConfigStatus.Store(message.RemoteConfigStatus)
-				}
-
-				return &protobufs.ServerToAgent{}
-			},
-		})
-
-	s := newSupervisor(t, "report_status", map[string]string{
-		"url":                  server.addr,
-		"config_apply_timeout": "3s",
-	})
-	require.Nil(t, s.Start())
-	defer s.Shutdown()
-
-	waitForSupervisorConnection(server.supervisorConnected, true)
-
-	cfg, hash, inputFile, outputFile := createSimplePipelineCollectorConf(t)
-
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: cfg.Bytes()},
-				},
-			},
-			ConfigHash: hash,
+	modes := []struct {
+		name               string
+		UseHUPConfigReload bool
+	}{
+		{
+			name:               "ProcessRestart",
+			UseHUPConfigReload: false,
 		},
-	})
+		{
+			name:               "HUPRestart",
+			UseHUPConfigReload: true,
+		},
+	}
 
-	// Check that the status is set to APPLYING
-	require.Eventually(t, func() bool {
-		status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLYING
-	}, 5*time.Second, 100*time.Millisecond, "Remote config status was not set to APPLYING")
+	for _, mode := range modes {
+		t.Run(mode.name, func(t *testing.T) {
+			var agentConfig atomic.Value
+			var healthReport atomic.Value
+			var remoteConfigStatus atomic.Value
+			server := newOpAMPServer(
+				t,
+				defaultConnectingHandler,
+				types.ConnectionCallbacks{
+					OnMessage: func(_ context.Context, _ types.Connection, message *protobufs.AgentToServer) *protobufs.ServerToAgent {
+						if message.EffectiveConfig != nil {
+							config := message.EffectiveConfig.ConfigMap.ConfigMap[""]
+							if config != nil {
+								agentConfig.Store(string(config.Body))
+							}
+						}
+						if message.Health != nil {
+							healthReport.Store(message.Health)
+						}
+						if message.RemoteConfigStatus != nil {
+							remoteConfigStatus.Store(message.RemoteConfigStatus)
+						}
 
-	// Wait for collector to become healthy
-	require.Eventually(t, func() bool {
-		health, ok := healthReport.Load().(*protobufs.ComponentHealth)
-		return ok && health.Healthy
-	}, 10*time.Second, 10*time.Millisecond, "Collector did not become healthy")
+						return &protobufs.ServerToAgent{}
+					},
+				})
 
-	// Check that the status is set to APPLIED
-	require.Eventually(t, func() bool {
-		status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED
-	}, 5*time.Second, 10*time.Millisecond, "Remote config status was not set to APPLIED")
+			extraConfigData := map[string]string{
+				"url":                  server.addr,
+				"config_apply_timeout": "3s",
+			}
+			if mode.UseHUPConfigReload {
+				extraConfigData["use_hup_config_reload"] = "true"
+			}
 
-	require.Eventually(t, func() bool {
-		cfg, ok := agentConfig.Load().(string)
-		if ok {
-			// The effective config may be structurally different compared to what was sent,
-			// and will also have some data redacted,
-			// so just check that it includes the filelog receiver
-			return strings.Contains(cfg, "filelog")
-		}
+			s, supervisorCfg := newSupervisor(t, "report_status", extraConfigData)
+			if mode.UseHUPConfigReload {
+				require.True(t, supervisorCfg.Agent.UseHUPConfigReload)
+			}
+			require.Nil(t, s.Start())
+			defer s.Shutdown()
 
-		return false
-	}, 5*time.Second, 10*time.Millisecond, "Collector was not started with remote config")
+			waitForSupervisorConnection(server.supervisorConnected, true)
 
-	n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
-	require.NotZero(t, n, "Could not write to input file")
-	require.NoError(t, err)
+			cfg, hash, inputFile, outputFile := createSimplePipelineCollectorConf(t)
 
-	require.Eventually(t, func() bool {
-		logRecord := make([]byte, 1024)
-		n, _ := outputFile.Read(logRecord)
+			server.sendToSupervisor(&protobufs.ServerToAgent{
+				RemoteConfig: &protobufs.AgentRemoteConfig{
+					Config: &protobufs.AgentConfigMap{
+						ConfigMap: map[string]*protobufs.AgentConfigFile{
+							"": {Body: cfg.Bytes()},
+						},
+					},
+					ConfigHash: hash,
+				},
+			})
 
-		return n != 0
-	}, 10*time.Second, 100*time.Millisecond, "Log never appeared in output")
+			// Check that the status is set to APPLYING
+			require.Eventually(t, func() bool {
+				status := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
+				t.Log("status", status.Status)
+				return status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLYING
+			}, 5*time.Second, 100*time.Millisecond, "Remote config status was not set to APPLYING")
 
-	// Test with bad configuration
-	badCfg, badHash := createBadCollectorConf(t)
+			// Wait for collector to become healthy
+			require.Eventually(t, func() bool {
+				health, ok := healthReport.Load().(*protobufs.ComponentHealth)
+				return ok && health.Healthy
+			}, 10*time.Second, 10*time.Millisecond, "Collector did not become healthy")
 
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{
-					"": {Body: badCfg.Bytes()},
-				},
-			},
-			ConfigHash: badHash,
-		},
-	})
+			// Check that the status is set to APPLIED
+			require.Eventually(t, func() bool {
+				status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
+				return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED
+			}, 5*time.Second, 100*time.Millisecond, "Remote config status was not set to APPLIED")
 
-	// Check that the status is set to APPLYING
-	require.Eventually(t, func() bool {
-		status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLYING
-	}, 5*time.Second, 200*time.Millisecond, "Remote config status was not set to APPLYING for bad config")
+			require.Eventually(t, func() bool {
+				cfg, ok := agentConfig.Load().(string)
+				if ok {
+					// The effective config may be structurally different compared to what was sent,
+					// and will also have some data redacted,
+					// so just check that it includes the filelog receiver
+					return strings.Contains(cfg, "filelog")
+				}
 
-	// Wait for the health checks to fail
-	require.Eventually(t, func() bool {
-		health, ok := healthReport.Load().(*protobufs.ComponentHealth)
-		return ok && !health.Healthy
-	}, 30*time.Second, 100*time.Millisecond, "Collector did not become unhealthy with bad config")
+				return false
+			}, 5*time.Second, 10*time.Millisecond, "Collector was not started with remote config")
 
-	// Check that the status is set to FAILED after failed health checks
-	require.Eventually(t, func() bool {
-		status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED
-	}, 15*time.Second, 100*time.Millisecond, "Remote config status was not set to FAILED for bad config")
+			n, err := inputFile.WriteString("{\"body\":\"hello, world\"}\n")
+			require.NotZero(t, n, "Could not write to input file")
+			require.NoError(t, err)
 
-	// Test with nop configuration
-	emptyHash := sha256.Sum256([]byte{})
-	server.sendToSupervisor(&protobufs.ServerToAgent{
-		RemoteConfig: &protobufs.AgentRemoteConfig{
-			Config: &protobufs.AgentConfigMap{
-				ConfigMap: map[string]*protobufs.AgentConfigFile{},
-			},
-			ConfigHash: emptyHash[:],
-		},
-	})
+			require.Eventually(t, func() bool {
+				logRecord := make([]byte, 1024)
+				n, _ := outputFile.Read(logRecord)
+
+				return n != 0
+			}, 10*time.Second, 100*time.Millisecond, "Log never appeared in output")
+
+			t.Run("bad config", func(t *testing.T) {
+				// Test with bad configuration
+				badCfg, badHash := createBadCollectorConf(t)
+
+				server.sendToSupervisor(&protobufs.ServerToAgent{
+					RemoteConfig: &protobufs.AgentRemoteConfig{
+						Config: &protobufs.AgentConfigMap{
+							ConfigMap: map[string]*protobufs.AgentConfigFile{
+								"": {Body: badCfg.Bytes()},
+							},
+						},
+						ConfigHash: badHash,
+					},
+				})
 
-	// Check that the status is set to APPLIED
-	require.Eventually(t, func() bool {
-		status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
-		return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED
-	}, 5*time.Second, 10*time.Millisecond, "Remote config status was not set to APPLIED for empty config")
+				// Check that the status is set to APPLYING
+				require.Eventually(t, func() bool {
+					status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
+					return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLYING
+				}, 5*time.Second, 200*time.Millisecond, "Remote config status was not set to APPLYING for bad config")
+
+				// Wait for the health checks to fail
+				require.Eventually(t, func() bool {
+					health, ok := healthReport.Load().(*protobufs.ComponentHealth)
+					return ok && !health.Healthy
+				}, 30*time.Second, 100*time.Millisecond, "Collector did not become unhealthy with bad config")
+
+				// Check that the status is set to FAILED after failed health checks
+				require.Eventually(t, func() bool {
+					status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
+					return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED
+				}, 15*time.Second, 100*time.Millisecond, "Remote config status was not set to FAILED for bad config")
+
+				// Test with nop configuration
+				emptyHash := sha256.Sum256([]byte{})
+				server.sendToSupervisor(&protobufs.ServerToAgent{
+					RemoteConfig: &protobufs.AgentRemoteConfig{
+						Config: &protobufs.AgentConfigMap{
+							ConfigMap: map[string]*protobufs.AgentConfigFile{},
+						},
+						ConfigHash: emptyHash[:],
+					},
+				})
+
+				// Check that the status is set to APPLIED
+				require.Eventually(t, func() bool {
+					status, ok := remoteConfigStatus.Load().(*protobufs.RemoteConfigStatus)
+					return ok && status.Status == protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED
+				}, 5*time.Second, 10*time.Millisecond, "Remote config status was not set to APPLIED for empty config")
+			})
+		})
+	}
 }
 
 func TestSupervisorOpAmpServerPort(t *testing.T) {
@@ -1998,7 +2202,7 @@ func TestSupervisorOpAmpServerPort(t *testing.T) {
 	supervisorOpAmpServerPort, err := findRandomPort()
 	require.NoError(t, err)
 
-	s := newSupervisor(t, "server_port", map[string]string{"url": server.addr, "supervisor_opamp_server_port": fmt.Sprintf("%d", supervisorOpAmpServerPort)})
+	s, _ := newSupervisor(t, "server_port", map[string]string{"url": server.addr, "supervisor_opamp_server_port": fmt.Sprintf("%d", supervisorOpAmpServerPort)})
 
 	require.Nil(t, s.Start())
 	defer s.Shutdown()
@@ -2110,7 +2314,7 @@ func TestSupervisorEmitBootstrapTelemetry(t *testing.T) {
 	defer mockBackend.Stop()
 	require.NoError(t, mockBackend.Start())
 
-	s := newSupervisor(t,
+	s, _ := newSupervisor(t,
 		"emit_telemetry",
 		map[string]string{
 			"url":          server.addr,
diff --git a/cmd/opampsupervisor/supervisor/supervisor_test.go b/cmd/opampsupervisor/supervisor/supervisor_test.go
index d3133d6910cf3..83a523f78843b 100644
--- a/cmd/opampsupervisor/supervisor/supervisor_test.go
+++ b/cmd/opampsupervisor/supervisor/supervisor_test.go
@@ -912,6 +912,8 @@ func Test_handleAgentOpAMPMessage(t *testing.T) {
 			opampClient:                  client,
 			customMessageToServer:        make(chan *protobufs.CustomMessage, 10),
 			doneChan:                     make(chan struct{}),
+			agentReadyChan:               make(chan struct{}),
+			agentReady:                   atomic.Bool{},
 		}
 
 		loopDoneChan := make(chan struct{})
@@ -1106,6 +1108,8 @@ func Test_handleAgentOpAMPMessage(t *testing.T) {
 			opampClient:                  mc,
 			customMessageToServer:        make(chan *protobufs.CustomMessage, 10),
 			doneChan:                     make(chan struct{}),
+			agentReadyChan:               make(chan struct{}),
+			agentReady:                   atomic.Bool{},
 		}
 
 		s.handleAgentOpAMPMessage(&mockConn{}, &protobufs.AgentToServer{
diff --git a/cmd/opampsupervisor/testdata/supervisor/supervisor_basic.yaml b/cmd/opampsupervisor/testdata/supervisor/supervisor_basic.yaml
index 97e7a671517e4..175aa1f7c9c09 100644
--- a/cmd/opampsupervisor/testdata/supervisor/supervisor_basic.yaml
+++ b/cmd/opampsupervisor/testdata/supervisor/supervisor_basic.yaml
@@ -20,3 +20,6 @@ agent:
   config_files:
     - {{ .local_config }}
   {{- end }}
+  {{- if .use_hup_config_reload }}
+  use_hup_config_reload: {{ .use_hup_config_reload }}
+  {{- end }}
diff --git a/cmd/opampsupervisor/testdata/supervisor/supervisor_healthcheck_port.yaml b/cmd/opampsupervisor/testdata/supervisor/supervisor_healthcheck_port.yaml
index 97e7a671517e4..175aa1f7c9c09 100644
--- a/cmd/opampsupervisor/testdata/supervisor/supervisor_healthcheck_port.yaml
+++ b/cmd/opampsupervisor/testdata/supervisor/supervisor_healthcheck_port.yaml
@@ -20,3 +20,6 @@ agent:
   config_files:
     - {{ .local_config }}
   {{- end }}
+  {{- if .use_hup_config_reload }}
+  use_hup_config_reload: {{ .use_hup_config_reload }}
+  {{- end }}
diff --git a/cmd/opampsupervisor/testdata/supervisor/supervisor_persistence.yaml b/cmd/opampsupervisor/testdata/supervisor/supervisor_persistence.yaml
index 8426885e4fee1..cac50f4087bd5 100644
--- a/cmd/opampsupervisor/testdata/supervisor/supervisor_persistence.yaml
+++ b/cmd/opampsupervisor/testdata/supervisor/supervisor_persistence.yaml
@@ -15,3 +15,6 @@ storage:
 
 agent:
   executable: ../../bin/otelcontribcol_{{.goos}}_{{.goarch}}{{.extension}}
+  {{- if .use_hup_config_reload }}
+  use_hup_config_reload: {{ .use_hup_config_reload }}
+  {{- end }}
diff --git a/cmd/opampsupervisor/testdata/supervisor/supervisor_report_status.yaml b/cmd/opampsupervisor/testdata/supervisor/supervisor_report_status.yaml
index f615ecf955748..9a9bc300cf0d0 100644
--- a/cmd/opampsupervisor/testdata/supervisor/supervisor_report_status.yaml
+++ b/cmd/opampsupervisor/testdata/supervisor/supervisor_report_status.yaml
@@ -19,3 +19,6 @@ storage:
 agent:
   executable: ../../bin/otelcontribcol_{{.goos}}_{{.goarch}}{{.extension}}
   config_apply_timeout: {{.config_apply_timeout}}
+  {{- if .use_hup_config_reload }}
+  use_hup_config_reload: {{ .use_hup_config_reload }}
+  {{- end }}