Repository: kolide/launcher

Issue:
Should wait until Windows exits Modern Standby to reload KATC extension
https://github.com/kolide/launcher/pull/2128 was a helpful improvement to avoid restarting the osquery instance if we can help it when the KATC configuration changes. However, it didn't take into account modern standby on Windows.

@zackattack01 noticed that when his Windows device is in modern standby and launcher receives an updated KATC configuration, the osquery instance can die and not be able to be restarted by launcher.

The logs report that the KATC extension was successfully updated in place -- `KATC configuration changed, successfully updated in-place without restarting instances` -- but then the osquery instance receives an i/o timeout when registering the KATC extension, and this causes the instance to shut down. Then, because we're in modern standby, the osquery runner is unable to successfully restart the osquery instance.

We should delay the KATC extension reload until the device exits modern standby.

Discussion:

Solution patch:
diff --git a/cmd/make/make.go b/cmd/make/make.go
index 1bddae42..be871801 100644
--- a/cmd/make/make.go
+++ b/cmd/make/make.go
@@ -22,16 +22,17 @@ func main() {
 	fs := flag.NewFlagSet("make", flag.ExitOnError)
 
 	var (
-		flTargets      = fs.String("targets", buildAll, "comma separated list of targets")
-		flDebug        = fs.Bool("debug", false, "use a debug logger")
-		flBuildARCH    = fs.String("arch", runtime.GOARCH, "Architecture to build for.")
-		flBuildOS      = fs.String("os", runtime.GOOS, "Operating system to build for.")
-		flGoPath       = fs.String("go", "", "Path for go binary. Will attempt auto detection.")
-		flRace         = fs.Bool("race", false, "Build race-detector version of binaries.")
-		flStatic       = fs.Bool("static", false, "Build a static binary.")
-		flStampVersion = fs.Bool("linkstamp", false, "Add version info with ldflags.")
-		flFakeData     = fs.Bool("fakedata", false, "Compile with build tags to falsify some data, like serial numbers")
-		flGithubOutput = fs.Bool("github", os.Getenv("GITHUB_ACTIONS") != "", "Include github action output")
+		flTargets        = fs.String("targets", buildAll, "comma separated list of targets")
+		flDebug          = fs.Bool("debug", false, "use a debug logger")
+		flBuildARCH      = fs.String("arch", runtime.GOARCH, "Architecture to build for.")
+		flBuildOS        = fs.String("os", runtime.GOOS, "Operating system to build for.")
+		flGoPath         = fs.String("go", "", "Path for go binary. Will attempt auto detection.")
+		flRace           = fs.Bool("race", false, "Build race-detector version of binaries.")
+		flStatic         = fs.Bool("static", false, "Build a static binary.")
+		flStampVersion   = fs.Bool("linkstamp", false, "Add version info with ldflags.")
+		flFakeData       = fs.Bool("fakedata", false, "Compile with build tags to falsify some data, like serial numbers")
+		flGithubOutput   = fs.Bool("github", os.Getenv("GITHUB_ACTIONS") != "", "Include github action output")
+		flIncludeSymbols = fs.Bool("debugsymbols", false, "Compile with or without debug symbols (stripped by default)")
 	)
 
 	ffOpts := []ff.Option{
@@ -74,6 +75,10 @@ func main() {
 		opts = append(opts, make.WithGithubActionOutput())
 	}
 
+	if *flIncludeSymbols {
+		opts = append(opts, make.WithOutStripped())
+	}
+
 	// We need to avoid cgo on windows. See
 	// https://github.com/golang/go/issues/22439 which still
 	// appears current, at least with cgo. But, we need to use cgo
diff --git a/ee/debug/checkups/checkups.go b/ee/debug/checkups/checkups.go
index e74266cd..e7211d3b 100644
--- a/ee/debug/checkups/checkups.go
+++ b/ee/debug/checkups/checkups.go
@@ -105,6 +105,7 @@ func checkupsFor(k types.Knapsack, target targetBits) []checkupInt {
 		{&osqConfigConflictCheckup{}, doctorSupported | flareSupported},
 		{&serverDataCheckup{k: k}, flareSupported | logSupported | startupLogSupported},
 		{&osqDataCollector{k: k}, doctorSupported | flareSupported},
+		{&intuneCheckup{}, flareSupported},
 		{&osqRestartCheckup{k: k}, doctorSupported | flareSupported},
 		{&uninstallHistoryCheckup{k: k}, flareSupported},
 		{&desktopMenu{k: k}, flareSupported},
diff --git a/ee/debug/checkups/intune_other.go b/ee/debug/checkups/intune_other.go
new file mode 100644
index 00000000..ce3b51f1
--- /dev/null
+++ b/ee/debug/checkups/intune_other.go
@@ -0,0 +1,35 @@
+//go:build !windows
+// +build !windows
+
+package checkups
+
+import (
+	"context"
+	"io"
+)
+
+type intuneCheckup struct{}
+
+func (i *intuneCheckup) Name() string {
+	return ""
+}
+
+func (i *intuneCheckup) Run(_ context.Context, _ io.Writer) error {
+	return nil
+}
+
+func (i *intuneCheckup) ExtraFileName() string {
+	return ""
+}
+
+func (i *intuneCheckup) Status() Status {
+	return Informational
+}
+
+func (i *intuneCheckup) Summary() string {
+	return ""
+}
+
+func (i *intuneCheckup) Data() any {
+	return nil
+}
diff --git a/ee/debug/checkups/intune_windows.go b/ee/debug/checkups/intune_windows.go
new file mode 100644
index 00000000..75bca78d
--- /dev/null
+++ b/ee/debug/checkups/intune_windows.go
@@ -0,0 +1,105 @@
+//go:build windows
+// +build windows
+
+package checkups
+
+import (
+	"archive/zip"
+	"context"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+type intuneCheckup struct {
+	summary string
+}
+
+func (i *intuneCheckup) Name() string {
+	return "Intune"
+}
+
+func (i *intuneCheckup) Run(ctx context.Context, extraWriter io.Writer) error {
+	// Other areas of interest: https://learn.microsoft.com/en-us/mem/intune/remote-actions/collect-diagnostics
+
+	zipWriter := zip.NewWriter(extraWriter)
+	defer zipWriter.Close()
+
+	if found, err := agentLogs(zipWriter); err != nil {
+		i.summary += fmt.Sprintf("Failed to collect Intune agent logs: %v. ", err)
+	} else if !found {
+		i.summary += "Intune agent logs not found. "
+	} else {
+		i.summary += "Intune agent logs found. "
+	}
+
+	if found, err := installLogs(zipWriter); err != nil {
+		i.summary += fmt.Sprintf("Failed to collect Intune install logs: %v. ", err)
+	} else if !found {
+		i.summary += "Intune install logs not found. "
+	} else {
+		i.summary += "Intune install logs found. "
+	}
+
+	i.summary = strings.TrimSpace(i.summary)
+
+	return nil
+}
+
+func agentLogs(zipWriter *zip.Writer) (bool, error) {
+	agentLogsPathPattern := filepath.Join(os.Getenv("SYSTEMROOT"), "ProgramData", "Microsoft", "IntuneManagementExtension", "Logs", "*")
+	matches, err := filepath.Glob(agentLogsPathPattern)
+	if err != nil {
+		return false, fmt.Errorf("globbing for agent logs at %s: %w", agentLogsPathPattern, err)
+	}
+	if len(matches) == 0 {
+		// Probably a non-Intune machine!
+		return false, nil
+	}
+
+	for _, match := range matches {
+		if err := addFileToZip(zipWriter, match); err != nil {
+			return true, fmt.Errorf("adding %s to zip: %w", match, err)
+		}
+	}
+
+	return true, nil
+}
+
+func installLogs(zipWriter *zip.Writer) (bool, error) {
+	installLogsPathPattern := filepath.Join(os.Getenv("WINDIR"), "System32", "config", "systemprofile", "AppData", "Local", "mdm", "*.log")
+	matches, err := filepath.Glob(installLogsPathPattern)
+	if err != nil {
+		return false, fmt.Errorf("globbing for install logs at %s: %w", installLogsPathPattern, err)
+	}
+	if len(matches) == 0 {
+		// Probably a non-Intune machine!
+		return false, nil
+	}
+
+	for _, match := range matches {
+		if err := addFileToZip(zipWriter, match); err != nil {
+			return true, fmt.Errorf("adding %s to zip: %w", match, err)
+		}
+	}
+
+	return true, nil
+}
+
+func (i *intuneCheckup) ExtraFileName() string {
+	return "intune.zip"
+}
+
+func (i *intuneCheckup) Status() Status {
+	return Informational
+}
+
+func (i *intuneCheckup) Summary() string {
+	return i.summary
+}
+
+func (i *intuneCheckup) Data() any {
+	return nil
+}
diff --git a/pkg/make/builder.go b/pkg/make/builder.go
index ffac2942..58471b19 100644
--- a/pkg/make/builder.go
+++ b/pkg/make/builder.go
@@ -292,6 +292,14 @@ func (b *Builder) BuildCmd(src, appName string) func(context.Context) error {
 
 		if !b.notStripped {
 			ldFlags = append(ldFlags, "-w -s")
+		} else {
+			level.Info(logger).Log(
+				"msg", "compiling debug build without stripping symbols",
+				"os", b.os,
+				"arch", b.arch,
+			)
+			// also include the following gcflags to -N (disable optimizations) and -l (disable inlining) for improved debugging
+			baseArgs = append(baseArgs, `-gcflags=all=-N -l`)
 		}
 
 		if b.os == "windows" {
diff --git a/pkg/osquery/runtime/runner.go b/pkg/osquery/runtime/runner.go
index c8eec6aa..d4741dba 100644
--- a/pkg/osquery/runtime/runner.go
+++ b/pkg/osquery/runtime/runner.go
@@ -36,6 +36,8 @@ type Runner struct {
 	opts            []OsqueryInstanceOption // global options applying to all osquery instances
 	shutdown        chan struct{}
 	interrupted     *atomic.Bool
+	needsRestart    *atomic.Bool
+	restartLock     sync.Mutex // use a restart lock to ensure we don't get multiple quick succession restarts due to in modern standy flapping
 }
 
 func New(k types.Knapsack, serviceClient service.KolideService, settingsWriter settingsStoreWriter, opts ...OsqueryInstanceOption) *Runner {
@@ -49,10 +51,15 @@ func New(k types.Knapsack, serviceClient service.KolideService, settingsWriter s
 		shutdown:        make(chan struct{}),
 		opts:            opts,
 		interrupted:     &atomic.Bool{},
+		needsRestart:    &atomic.Bool{},
 	}
 
 	k.RegisterChangeObserver(runner,
-		keys.WatchdogEnabled, keys.WatchdogMemoryLimitMB, keys.WatchdogUtilizationLimitPercent, keys.WatchdogDelaySec,
+		keys.WatchdogEnabled,
+		keys.WatchdogMemoryLimitMB,
+		keys.WatchdogUtilizationLimitPercent,
+		keys.WatchdogDelaySec,
+		keys.InModernStandby, // we delay restarts while in modern standby, so we need to be notified of changes to perform restarts on wake
 	)
 
 	return runner
@@ -269,20 +276,40 @@ func (r *Runner) triggerShutdownForInstances(ctx context.Context) error {
 }
 
 // FlagsChanged satisfies the types.FlagsChangeObserver interface -- handles updates to flags
-// that we care about, which are enable_watchdog, watchdog_delay_sec, watchdog_memory_limit_mb,
+// that we care about, which are enable_watchdog, watchdog_delay_sec, watchdog_memory_limit_mb, in_modern_standby,
 // and watchdog_utilization_limit_percent.
 func (r *Runner) FlagsChanged(ctx context.Context, flagKeys ...keys.FlagKey) {
 	ctx, span := observability.StartSpan(ctx)
 	defer span.End()
 
+	r.restartLock.Lock()
+
+	// only modern standby changed and no restart pending
+	if len(flagKeys) == 1 && flagKeys[0] == keys.InModernStandby && !r.needsRestart.Load() {
+		r.slogger.Log(ctx, slog.LevelDebug,
+			"only modern standby flag changed and no restart needed, doing nothing",
+			"in_modern_standby", r.knapsack.InModernStandby(),
+			"needs_restart", r.needsRestart.Load(),
+		)
+
+		r.restartLock.Unlock()
+		return
+	}
+
+	r.restartLock.Unlock()
+
 	r.slogger.Log(ctx, slog.LevelDebug,
-		"control server flags changed, restarting instance to apply",
+		"restarting osquery instances due to flag changes or needed restart",
 		"flags", fmt.Sprintf("%+v", flagKeys),
+		"in_modern_standby", r.knapsack.InModernStandby(),
+		"needs_restart", r.needsRestart.Load(),
 	)
 
+	// r.Restart will check if we are in modern standby and set the needsRestart flag if so and we will restart
+	// when we exit modern standby
 	if err := r.Restart(ctx); err != nil {
 		r.slogger.Log(ctx, slog.LevelError,
-			"could not restart osquery instance after flag change",
+			"could not restart osquery instance after flag change or needed restart",
 			"err", err,
 		)
 	}
@@ -329,14 +356,33 @@ func (r *Runner) Ping() {
 
 // Restart allows you to cleanly shutdown the current instance and launch a new
 // instance with the same configurations.
+// If we are in modern standby, we will not restart now, but will instead
+// set a flag to indicate that we should restart when we exit modern standby.
 func (r *Runner) Restart(ctx context.Context) error {
 	ctx, span := observability.StartSpan(ctx)
 	defer span.End()
 
+	r.restartLock.Lock()
+	defer r.restartLock.Unlock()
+
 	r.slogger.Log(ctx, slog.LevelDebug,
 		"runner.Restart called",
 	)
 
+	// check to see if we are in modern standby; if so, we cannot restart now
+	// when we exit modern standby, we'll get a call to FlagsChanged which will
+	// trigger a restart then
+	if r.knapsack.InModernStandby() {
+		r.slogger.Log(ctx, slog.LevelInfo,
+			"device is in modern standby, not restarting osquery instances, will apply changes on wake",
+		)
+
+		r.needsRestart.Store(true)
+		return nil
+	}
+
+	r.needsRestart.Store(false)
+
 	// Shut down the instances -- this will trigger a restart in each `runInstance`.
 	if err := r.triggerShutdownForInstances(ctx); err != nil {
 		return fmt.Errorf("triggering shutdown for instances during runner restart: %w", err)



Test patch:
diff --git a/pkg/osquery/runtime/runtime_posix_test.go b/pkg/osquery/runtime/runtime_posix_test.go
index 42d3bf6a..7aac38b5 100644
--- a/pkg/osquery/runtime/runtime_posix_test.go
+++ b/pkg/osquery/runtime/runtime_posix_test.go
@@ -50,7 +50,7 @@ func TestOsquerySlowStart(t *testing.T) {
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
 	k.On("OsqueryVerbose").Return(true).Maybe()
 	k.On("OsqueryFlags").Return([]string{}).Maybe()
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("LoggingInterval").Return(5 * time.Minute).Maybe()
@@ -113,7 +113,7 @@ func TestExtensionSocketPath(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
 	k.On("OsqueryVerbose").Return(true).Maybe()
diff --git a/pkg/osquery/runtime/runtime_test.go b/pkg/osquery/runtime/runtime_test.go
index ef36be36..432ef2cf 100644
--- a/pkg/osquery/runtime/runtime_test.go
+++ b/pkg/osquery/runtime/runtime_test.go
@@ -123,7 +123,7 @@ func TestBadBinaryPath(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return("") // bad binary path
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -174,7 +174,7 @@ func TestWithOsqueryFlags(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -225,7 +225,7 @@ func TestFlagsChanged(t *testing.T) {
 	k.On("WatchdogMemoryLimitMB").Return(150)
 	k.On("WatchdogUtilizationLimitPercent").Return(20)
 	k.On("WatchdogDelaySec").Return(120)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -235,7 +235,6 @@ func TestFlagsChanged(t *testing.T) {
 	k.On("LogMaxBytesPerBatch").Return(0).Maybe()
 	k.On("Transport").Return("jsonrpc").Maybe()
 	k.On("ReadEnrollSecret").Return("", nil).Maybe()
-	k.On("InModernStandby").Return(false).Maybe()
 	k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe()
 	k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe()
 	k.On("RegisterChangeObserver", mock.Anything, keys.PinnedOsquerydVersion).Maybe()
@@ -255,6 +254,9 @@ func TestFlagsChanged(t *testing.T) {
 	s := settingsstoremock.NewSettingsStoreWriter(t)
 	s.On("WriteSettings").Return(nil)
 
+	// set to false initially so osq can start
+	k.On("InModernStandby").Return(false).Twice()
+
 	// Start the runner
 	runner := New(k, mockServiceClient(t), s)
 	ensureShutdownOnCleanup(t, runner, logBytes)
@@ -275,9 +277,24 @@ func TestFlagsChanged(t *testing.T) {
 
 	startingInstance := runner.instances[types.DefaultRegistrationID]
 
-	// Now, WatchdogEnabled should return true
+	// should start off false
+	require.False(t, runner.needsRestart.Load(), "runner should not be flagged as needing restart since it just started")
+
+	// change just the InModernStandby flag -- this should not trigger a restart, since no changes need to be applied
+	k.On("InModernStandby").Return(true).Once()
+	runner.FlagsChanged(context.TODO(), keys.InModernStandby)
+	require.False(t, runner.needsRestart.Load(), "runner should not be marked as needing a restart when only InModernStandby changed")
+
+	// change both the InModernStandby and WatchdogEnabled flags -- this should trigger a restart, but not until InModernStandby is false again
 	k.On("WatchdogEnabled").Return(true).Once()
-	runner.FlagsChanged(context.TODO(), keys.WatchdogEnabled)
+	k.On("InModernStandby").Return(true).Twice()
+	runner.FlagsChanged(context.TODO(), keys.WatchdogEnabled, keys.InModernStandby)
+
+	require.True(t, runner.needsRestart.Load(), "runner should be marked as needing a restart after WatchdogEnabled changed while in modern standby")
+
+	// no simulate coming out of modern standby -- this should trigger a restart
+	k.On("InModernStandby").Return(false)
+	runner.FlagsChanged(context.TODO(), keys.InModernStandby)
 
 	// Wait for the instance to restart, then confirm it's healthy post-restart
 	time.Sleep(2 * time.Second)
@@ -286,6 +303,8 @@ func TestFlagsChanged(t *testing.T) {
 	// Now confirm that the instance is new
 	require.NotEqual(t, startingInstance, runner.instances[types.DefaultRegistrationID], "instance not replaced")
 
+	require.False(t, runner.needsRestart.Load(), "runner should no longer be marked as needing a restart after restart completed")
+
 	// Confirm osquery watchdog is now enabled
 	watchdogMemoryLimitMBFound := false
 	watchdogUtilizationLimitPercentFound := false
@@ -331,7 +350,7 @@ func TestPing(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -596,7 +615,7 @@ func TestSimplePath(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -647,7 +666,7 @@ func TestMultipleInstances(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID, extraRegistrationId})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -731,7 +750,7 @@ func TestRunnerHandlesImmediateShutdownWithMultipleInstances(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -810,7 +829,7 @@ func TestMultipleShutdowns(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
@@ -861,7 +880,7 @@ func TestOsqueryDies(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("WatchdogEnabled").Return(false)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory)
@@ -934,7 +953,7 @@ func TestNotStarted(t *testing.T) {
 	k.On("RegistrationIDs").Return([]string{types.DefaultRegistrationID})
 	k.On("OsqueryHealthcheckStartupDelay").Return(0 * time.Second).Maybe()
 	k.On("RootDirectory").Return(rootDirectory).Maybe()
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
 	k.On("Slogger").Return(multislogger.NewNopLogger())
 	setupHistory(t, k)
 	runner := New(k, mockServiceClient(t), settingsstoremock.NewSettingsStoreWriter(t))
@@ -1034,7 +1053,7 @@ func setupOsqueryInstanceForTests(t *testing.T) (runner *Runner, logBytes *threa
 	k.On("WatchdogMemoryLimitMB").Return(150)
 	k.On("WatchdogUtilizationLimitPercent").Return(20)
 	k.On("WatchdogDelaySec").Return(120)
-	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
+	k.On("RegisterChangeObserver", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe()
 	k.On("Slogger").Return(slogger)
 	k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary)
 	k.On("RootDirectory").Return(rootDirectory).Maybe()